From 19cf359a69e5aa720d0601361c3b48e5bb24a212 Mon Sep 17 00:00:00 2001 From: larryrider Date: Fri, 29 May 2026 17:54:17 +0200 Subject: [PATCH 01/11] feat: ignore mkcol error when folder already exists --- src/webdav/handlers/MKCOL.handler.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/webdav/handlers/MKCOL.handler.ts b/src/webdav/handlers/MKCOL.handler.ts index 0c45bbf2..931f605f 100644 --- a/src/webdav/handlers/MKCOL.handler.ts +++ b/src/webdav/handlers/MKCOL.handler.ts @@ -4,7 +4,6 @@ import { WebDavUtils } from '../../utils/webdav.utils'; import { webdavLogger } from '../../utils/logger.utils'; import { XMLUtils } from '../../utils/xml.utils'; import { WebDavFolderService } from '../../services/webdav/webdav-folder.service'; -import { MethodNotAllowed } from '../../utils/errors.utils'; import { AsyncUtils } from '../../utils/async.utils'; export class MKCOLRequestHandler implements WebDavMethodHandler { @@ -22,8 +21,9 @@ export class MKCOLRequestHandler implements WebDavMethodHandler { const folderAlreadyExists = !!driveFolderItem; if (folderAlreadyExists) { - webdavLogger.info(`[MKCOL] ❌ Folder '${resource.url}' already exists`); - throw new MethodNotAllowed('Folder already exists'); + webdavLogger.info(`[MKCOL] Folder '${resource.url}' already exists, ignoring the creation request`); + res.status(201).send(XMLUtils.toWebDavXML({}, {})); + return; } const newFolder = await WebDavFolderService.instance.createFolder({ From 853a0eba61647ff36d69262257bfe4f8989087a7 Mon Sep 17 00:00:00 2001 From: larryrider Date: Fri, 29 May 2026 17:59:01 +0200 Subject: [PATCH 02/11] feat: handle conflicts when uploading files to WebDAV, replacing NotFoundError with ConflictError for existing folders --- src/webdav/handlers/PUT.handler.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/webdav/handlers/PUT.handler.ts b/src/webdav/handlers/PUT.handler.ts index ffc32a7c..a822fc63 100644 --- a/src/webdav/handlers/PUT.handler.ts +++ b/src/webdav/handlers/PUT.handler.ts @@ -2,7 +2,7 @@ import { Request, Response } from 'express'; import { DriveFileService } from '../../services/drive/drive-file.service'; import { AuthService } from '../../services/auth.service'; import { WebDavMethodHandler } from '../../types/webdav.types'; -import { NotFoundError } from '../../utils/errors.utils'; +import { ConflictError } from '../../utils/errors.utils'; import { WebDavUtils } from '../../utils/webdav.utils'; import { webdavLogger } from '../../utils/logger.utils'; import { EncryptionVersion } from '@internxt/sdk/dist/drive/storage/types'; @@ -22,13 +22,6 @@ export class PUTRequestHandler implements WebDavMethodHandler { } const resource = await WebDavUtils.getRequestedResource(req.url); - - // If the file already exists, the WebDAV specification states that 'PUT /…/file' should replace it. - // http://www.webdav.org/specs/rfc4918.html#put-resources - const driveFileItem = await WebDavUtils.getDriveItemFromResource(resource); - if (driveFileItem?.itemType === 'folder') { - throw new NotFoundError('Folders cannot be created with PUT. Use MKCOL instead.'); - } webdavLogger.info(`[PUT] Request received for file at ${resource.url}`); webdavLogger.info( `[PUT] Uploading '${resource.name}' (${FormatUtils.humanFileSize(contentLength)}) to '${resource.parentPath}'`, @@ -44,15 +37,22 @@ export class PUTRequestHandler implements WebDavMethodHandler { (await WebDavFolderService.instance.getDriveFolderItemFromPath(resource.parentPath)) ?? (await WebDavFolderService.instance.createParentPathOrThrow(resource.parentPath)); - try { - if (driveFileItem && driveFileItem.status === 'EXISTS') { - webdavLogger.info( - `[PUT] File '${resource.name}' already exists in '${resource.path.dir}', it will be replaced...`, - ); + // If the file already exists, the WebDAV specification states that 'PUT /…/file' should replace it. + // http://www.webdav.org/specs/rfc4918.html#put-resources + const driveFileItem = await WebDavUtils.getDriveItemFromResource(resource); + if (driveFileItem && driveFileItem.status === 'EXISTS') { + if (driveFileItem.itemType === 'folder') { + webdavLogger.info('[PUT] ❌ A folder exists on the cloud with the same name.'); + throw new ConflictError('A folder exists on the cloud with the same name'); + } + webdavLogger.info( + `[PUT] File '${resource.name}' already exists in '${resource.path.dir}', it will be replaced...`, + ); + try { await WebDavUtils.deleteOrTrashItem(driveFileItem); + } catch { + //noop } - } catch { - //noop } const { user } = await AuthService.instance.getAuthDetails(); From f8b224aede659d86ec44ca99c9bc236b7311da8f Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 1 Jun 2026 15:47:52 +0200 Subject: [PATCH 03/11] feat: enhance AuthMiddleware to utilize caching for authentication details --- src/webdav/middewares/auth.middleware.ts | 14 +++++++-- .../middlewares/auth.middleware.test.ts | 29 +++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/webdav/middewares/auth.middleware.ts b/src/webdav/middewares/auth.middleware.ts index d2e2190f..75751af4 100644 --- a/src/webdav/middewares/auth.middleware.ts +++ b/src/webdav/middewares/auth.middleware.ts @@ -1,16 +1,26 @@ import { RequestHandler } from 'express'; import { SdkManager } from '../../services/sdk-manager.service'; import { AuthService } from '../../services/auth.service'; +import { CacheService } from '../../services/cache.service'; import { webdavLogger } from '../../utils/logger.utils'; import { XMLUtils } from '../../utils/xml.utils'; import { ErrorUtils } from '../../utils/errors.utils'; +import { LoginCredentials } from '../../types/command.types'; export const AuthMiddleware = (): RequestHandler => { return (_, res, next) => { (async () => { try { - const { token, workspace } = await AuthService.instance.getAuthDetails(); - SdkManager.init({ token, workspaceToken: workspace?.workspaceCredentials.token }); + const cached = CacheService.instance.get(CacheService.AUTH_CACHE_KEY); + + if (cached) { + SdkManager.init({ token: cached.token, workspaceToken: cached.workspace?.workspaceCredentials?.token }); + next(); + return; + } + + const authDetails = await AuthService.instance.getAuthDetails(); + CacheService.instance.set(CacheService.AUTH_CACHE_KEY, authDetails); next(); } catch (error) { let message = 'Authentication required to access this resource.'; diff --git a/test/webdav/middlewares/auth.middleware.test.ts b/test/webdav/middlewares/auth.middleware.test.ts index b2f475d6..d5ad6bab 100644 --- a/test/webdav/middlewares/auth.middleware.test.ts +++ b/test/webdav/middlewares/auth.middleware.test.ts @@ -1,12 +1,17 @@ -import { describe, expect, it, vi } from 'vitest'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; import { AuthMiddleware } from '../../../src/webdav/middewares/auth.middleware'; import { createWebDavRequestFixture, createWebDavResponseFixture } from '../../fixtures/webdav.fixture'; import { UserCredentialsFixture } from '../../fixtures/login.fixture'; import { AuthService } from '../../../src/services/auth.service'; +import { CacheService } from '../../../src/services/cache.service'; import { MissingCredentialsError } from '../../../src/types/command.types'; import { XMLUtils } from '../../../src/utils/xml.utils'; describe('Auth middleware', () => { + beforeEach(() => { + CacheService.instance.clearCaches(); + }); + it('When the user is not authenticated, then it should return 401', async () => { const req = createWebDavRequestFixture({}); const res = createWebDavResponseFixture({ @@ -34,7 +39,7 @@ describe('Auth middleware', () => { ); }); - it('When the user is authenticated, then it should call next', async () => { + it('When the user is authenticated, then it should call next and cache the result', async () => { const req = createWebDavRequestFixture({}); const res = createWebDavResponseFixture({}); const next = vi.fn(); @@ -46,5 +51,25 @@ describe('Auth middleware', () => { expect(next).toHaveBeenCalledOnce(); expect(res.status).not.toHaveBeenCalled(); expect(res.send).not.toHaveBeenCalled(); + + const cached = CacheService.instance.get(CacheService.AUTH_CACHE_KEY); + expect(cached).toEqual(UserCredentialsFixture); + }); + + it('When the auth details are cached, then it should not call getAuthDetails', async () => { + const req = createWebDavRequestFixture({}); + const res = createWebDavResponseFixture({}); + const next = vi.fn(); + + CacheService.instance.set(CacheService.AUTH_CACHE_KEY, UserCredentialsFixture); + + const authServiceStub = vi.spyOn(AuthService.instance, 'getAuthDetails'); + + await AuthMiddleware()(req, res, next); + + expect(authServiceStub).not.toHaveBeenCalled(); + expect(next).toHaveBeenCalledOnce(); + expect(res.status).not.toHaveBeenCalled(); + expect(res.send).not.toHaveBeenCalled(); }); }); From d38131411278f21b755692fafc75cac2251e4a79 Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 1 Jun 2026 15:51:58 +0200 Subject: [PATCH 04/11] feat: refactor cache key usage in UsageService to utilize CacheService constants --- src/services/cache.service.ts | 5 +++++ src/services/usage.service.ts | 15 ++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/services/cache.service.ts b/src/services/cache.service.ts index 1ea64b63..3caeb7d9 100644 --- a/src/services/cache.service.ts +++ b/src/services/cache.service.ts @@ -11,6 +11,11 @@ export class CacheService { private readonly store = new Map>(); private readonly defaultTtl = FIFTEEN_MINUTES; + public static readonly FETCH_USAGE_CACHE_KEY = 'usage:fetchUsage'; + public static readonly FETCH_SPACE_LIMIT_CACHE_KEY = 'usage:fetchSpaceLimit'; + public static readonly FETCH_LIMITS_CACHE_KEY = 'usage:fetchLimits'; + public static readonly AUTH_CACHE_KEY = 'auth:details'; + public get = (key: string): T | null => { const entry = this.store.get(key); if (!entry) return null; diff --git a/src/services/usage.service.ts b/src/services/usage.service.ts index a6fde31b..91e525a1 100644 --- a/src/services/usage.service.ts +++ b/src/services/usage.service.ts @@ -5,40 +5,37 @@ import { StorageTypes } from '@internxt/sdk/dist/drive/storage'; export class UsageService { public static readonly instance: UsageService = new UsageService(); public static readonly INFINITE_LIMIT = 99 * Math.pow(1024, 4); - private static readonly FETCH_USAGE_CACHE_KEY = 'usage:fetchUsage'; - private static readonly FETCH_SPACE_LIMIT_CACHE_KEY = 'usage:fetchSpaceLimit'; - private static readonly FETCH_LIMITS_CACHE_KEY = 'usage:fetchLimits'; public fetchUsage = async (): Promise => { - const cached = CacheService.instance.get(UsageService.FETCH_USAGE_CACHE_KEY); + const cached = CacheService.instance.get(CacheService.FETCH_USAGE_CACHE_KEY); if (cached !== null) return cached; const storageClient = SdkManager.instance.getStorage(); const driveUsage = await storageClient.spaceUsageV2(); - CacheService.instance.set(UsageService.FETCH_USAGE_CACHE_KEY, driveUsage.total); + CacheService.instance.set(CacheService.FETCH_USAGE_CACHE_KEY, driveUsage.total); return driveUsage.total; }; public fetchSpaceLimit = async (): Promise => { - const cached = CacheService.instance.get(UsageService.FETCH_SPACE_LIMIT_CACHE_KEY); + const cached = CacheService.instance.get(CacheService.FETCH_SPACE_LIMIT_CACHE_KEY); if (cached !== null) return cached; const storageClient = SdkManager.instance.getStorage(); const spaceLimit = await storageClient.spaceLimitV2(); - CacheService.instance.set(UsageService.FETCH_SPACE_LIMIT_CACHE_KEY, spaceLimit.maxSpaceBytes); + CacheService.instance.set(CacheService.FETCH_SPACE_LIMIT_CACHE_KEY, spaceLimit.maxSpaceBytes); return spaceLimit.maxSpaceBytes; }; public fetchLimits = async (): Promise => { - const cached = CacheService.instance.get(UsageService.FETCH_LIMITS_CACHE_KEY); + const cached = CacheService.instance.get(CacheService.FETCH_LIMITS_CACHE_KEY); if (cached !== null) return cached; const storageClient = SdkManager.instance.getStorage(); const limits = await storageClient.getFileVersionLimits(); - CacheService.instance.set(UsageService.FETCH_LIMITS_CACHE_KEY, limits); + CacheService.instance.set(CacheService.FETCH_LIMITS_CACHE_KEY, limits); return limits; }; } From 953f869a7c76a0e926ef7839eaf615727be50e64 Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 1 Jun 2026 16:17:02 +0200 Subject: [PATCH 05/11] feat: add check for dataSource initialization in DatabaseService --- src/services/database/database.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/database/database.service.ts b/src/services/database/database.service.ts index 43325ac3..074836bc 100644 --- a/src/services/database/database.service.ts +++ b/src/services/database/database.service.ts @@ -26,7 +26,9 @@ export class DatabaseService { ); public initialize = () => { - return this.dataSource.initialize(); + if (!this.dataSource.isInitialized) { + return this.dataSource.initialize(); + } }; public destroy = () => { From 70db484e59eacf95a49269dcf13eef5c2b40ffeb Mon Sep 17 00:00:00 2001 From: larryrider Date: Wed, 3 Jun 2026 18:59:49 +0200 Subject: [PATCH 06/11] feat: implement caching for authentication details in AuthService --- src/services/auth.service.ts | 7 +++++++ test/services/auth.service.test.ts | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 1da23337..376bc78b 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -80,6 +80,12 @@ export class AuthService { * @throws {ExpiredCredentialsError} When token has expired */ public getAuthDetails = async (): Promise => { + const cached = CacheService.instance.get(CacheService.AUTH_CACHE_KEY); + if (cached) { + SdkManager.init({ token: cached.token, workspaceToken: cached.workspace?.workspaceCredentials?.token }); + return cached; + } + let loginCreds = await ConfigService.instance.readUser(); if (!loginCreds?.token || !loginCreds?.user?.mnemonic) { throw new MissingCredentialsError(); @@ -113,6 +119,7 @@ export class AuthService { SdkManager.init({ token: loginCreds.token, workspaceToken: workspaceCreds?.workspaceCredentials.token }); await ConfigService.instance.saveUser(loginCreds); + CacheService.instance.set(CacheService.AUTH_CACHE_KEY, loginCreds); return loginCreds; }; diff --git a/test/services/auth.service.test.ts b/test/services/auth.service.test.ts index 5f95b0c1..0c7a9a74 100644 --- a/test/services/auth.service.test.ts +++ b/test/services/auth.service.test.ts @@ -16,11 +16,13 @@ import { import { UserCredentialsFixture } from '../fixtures/login.fixture'; import { fail } from 'node:assert'; import { paths } from '@internxt/sdk/dist/schema'; +import { CacheService } from '../../src/services/cache.service'; describe('Auth service', () => { beforeEach(() => { vi.spyOn(ConfigService.instance, 'readUser').mockResolvedValue(UserCredentialsFixture); vi.spyOn(ConfigService.instance, 'saveUser').mockResolvedValue(undefined); + vi.spyOn(CacheService.instance, 'get').mockReturnValue(undefined); }); it('When user logs in, then login user credentials are generated', async () => { From caa63580cf0ef331fbb2ac5e6b32f6bbd0b69abf Mon Sep 17 00:00:00 2001 From: larryrider Date: Wed, 3 Jun 2026 19:09:06 +0200 Subject: [PATCH 07/11] feat: standardize test descriptions in AuthService and AuthMiddleware tests --- test/services/auth.service.test.ts | 20 +++++++++---------- .../middlewares/auth.middleware.test.ts | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/test/services/auth.service.test.ts b/test/services/auth.service.test.ts index 0c7a9a74..8b0962f2 100644 --- a/test/services/auth.service.test.ts +++ b/test/services/auth.service.test.ts @@ -25,7 +25,7 @@ describe('Auth service', () => { vi.spyOn(CacheService.instance, 'get').mockReturnValue(undefined); }); - it('When user logs in, then login user credentials are generated', async () => { + it('should generate login user credentials when user logs in', async () => { const loginResponse = { token: crypto.randomBytes(16).toString('hex'), newToken: crypto.randomBytes(16).toString('hex'), @@ -50,7 +50,7 @@ describe('Auth service', () => { expect(responseLogin).to.be.deep.equal(expectedResponseLogin); }); - it('When user logs in and credentials are not correct, then an error is thrown', async () => { + it('should throw an error when user logs in and credentials are not correct', async () => { const loginDetails: LoginDetails = { email: crypto.randomBytes(16).toString('hex'), password: crypto.randomBytes(8).toString('hex'), @@ -69,7 +69,7 @@ describe('Auth service', () => { expect(loginStub).toHaveBeenCalledOnce(); }); - it('When two factor authentication is enabled, then it is returned from is2FANeeded functionality', async () => { + it('should return true from is2FANeeded when two factor authentication is enabled', async () => { const email = crypto.randomBytes(16).toString('hex'); const securityDetails: SecurityDetails = { encryptedSalt: crypto.randomBytes(16).toString('hex'), @@ -85,7 +85,7 @@ describe('Auth service', () => { expect(responseLogin).to.be.equal(securityDetails.tfaEnabled); }); - it('When email is not correct when checking two factor authentication, then an error is thrown', async () => { + it('should throw an error when checking two factor authentication with an incorrect email', async () => { const email = crypto.randomBytes(16).toString('hex'); const securityStub = vi.spyOn(Auth.prototype, 'securityDetails').mockRejectedValue(new Error()); @@ -100,7 +100,7 @@ describe('Auth service', () => { expect(securityStub).toHaveBeenCalledOnce(); }); - it('When getting auth details, should get them if all are found', async () => { + it('should return auth details when all credentials are found', async () => { const sut = AuthService.instance; const loginCreds: LoginCredentials = UserCredentialsFixture; @@ -127,7 +127,7 @@ describe('Auth service', () => { expect(result).to.deep.equal(loginCreds); }); - it('When credentials are missing, should throw an error', async () => { + it('should throw an error when credentials are missing', async () => { const sut = AuthService.instance; const readUserStub = vi.spyOn(ConfigService.instance, 'readUser').mockResolvedValue(undefined); @@ -141,7 +141,7 @@ describe('Auth service', () => { expect(readUserStub).toHaveBeenCalledOnce(); }); - it('When auth token is missing, should throw an error', async () => { + it('should throw an error when auth token is missing', async () => { const sut = AuthService.instance; const readUserStub = vi.spyOn(ConfigService.instance, 'readUser').mockResolvedValue({ @@ -159,7 +159,7 @@ describe('Auth service', () => { expect(readUserStub).toHaveBeenCalledOnce(); }); - it('When mnemonic is invalid, should throw an error', async () => { + it('should throw an error when mnemonic is invalid', async () => { const sut = AuthService.instance; const mockToken = { @@ -187,7 +187,7 @@ describe('Auth service', () => { expect(validateMnemonicStub).toHaveBeenCalledWith(UserCredentialsFixture.user.mnemonic); }); - it('When token has expired, should throw an error', async () => { + it('should throw an error when token has expired', async () => { const sut = AuthService.instance; const mockToken = { @@ -215,7 +215,7 @@ describe('Auth service', () => { expect(validateMnemonicStub).toHaveBeenCalledWith(UserCredentialsFixture.user.mnemonic); }); - it('When tokens are going to expire soon, then they are refreshed', async () => { + it('should refresh tokens when they are going to expire soon', async () => { const sut = AuthService.instance; const mockToken = { diff --git a/test/webdav/middlewares/auth.middleware.test.ts b/test/webdav/middlewares/auth.middleware.test.ts index d5ad6bab..cf153677 100644 --- a/test/webdav/middlewares/auth.middleware.test.ts +++ b/test/webdav/middlewares/auth.middleware.test.ts @@ -12,7 +12,7 @@ describe('Auth middleware', () => { CacheService.instance.clearCaches(); }); - it('When the user is not authenticated, then it should return 401', async () => { + it('should return 401 when the user is not authenticated', async () => { const req = createWebDavRequestFixture({}); const res = createWebDavResponseFixture({ status: vi.fn().mockReturnValue({ send: vi.fn() }), @@ -39,7 +39,7 @@ describe('Auth middleware', () => { ); }); - it('When the user is authenticated, then it should call next and cache the result', async () => { + it('should call next and cache the result when the user is authenticated', async () => { const req = createWebDavRequestFixture({}); const res = createWebDavResponseFixture({}); const next = vi.fn(); @@ -56,7 +56,7 @@ describe('Auth middleware', () => { expect(cached).toEqual(UserCredentialsFixture); }); - it('When the auth details are cached, then it should not call getAuthDetails', async () => { + it('should not call getAuthDetails when the auth details are cached', async () => { const req = createWebDavRequestFixture({}); const res = createWebDavResponseFixture({}); const next = vi.fn(); From 31efc58db0943854c92daef9227d68a7ada746eb Mon Sep 17 00:00:00 2001 From: larryrider Date: Wed, 3 Jun 2026 19:20:06 +0200 Subject: [PATCH 08/11] feat: update Dockerfile to use node:24-bookworm-slim and enhance healthcheck parameters --- Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5894a400..50c00f7f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM node:24-alpine +FROM node:24-bookworm-slim -RUN apk add --no-cache jq +RUN apt-get update && apt-get install -y jq && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY . . @@ -17,4 +17,4 @@ RUN ln -s '/app/bin/run.js' /usr/local/bin/internxt ENTRYPOINT ["/app/docker/entrypoint.sh"] -HEALTHCHECK --interval=60s --timeout=20s --start-period=30s --retries=3 CMD /app/docker/health_check.sh +HEALTHCHECK --interval=120s --timeout=30s --start-period=60s --retries=3 CMD /app/docker/health_check.sh From 0dca0ad0ddf9d043bf65177c196e3f8748955b3b Mon Sep 17 00:00:00 2001 From: larryrider Date: Thu, 4 Jun 2026 16:47:24 +0200 Subject: [PATCH 09/11] feat: moved caching mechanism from AuthService to ConfigService --- src/services/auth.service.ts | 7 ------- src/services/config.service.ts | 7 +++++++ test/services/config.service.test.ts | 3 +++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 376bc78b..1da23337 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -80,12 +80,6 @@ export class AuthService { * @throws {ExpiredCredentialsError} When token has expired */ public getAuthDetails = async (): Promise => { - const cached = CacheService.instance.get(CacheService.AUTH_CACHE_KEY); - if (cached) { - SdkManager.init({ token: cached.token, workspaceToken: cached.workspace?.workspaceCredentials?.token }); - return cached; - } - let loginCreds = await ConfigService.instance.readUser(); if (!loginCreds?.token || !loginCreds?.user?.mnemonic) { throw new MissingCredentialsError(); @@ -119,7 +113,6 @@ export class AuthService { SdkManager.init({ token: loginCreds.token, workspaceToken: workspaceCreds?.workspaceCredentials.token }); await ConfigService.instance.saveUser(loginCreds); - CacheService.instance.set(CacheService.AUTH_CACHE_KEY, loginCreds); return loginCreds; }; diff --git a/src/services/config.service.ts b/src/services/config.service.ts index bdf6eb1b..2ba07442 100644 --- a/src/services/config.service.ts +++ b/src/services/config.service.ts @@ -17,6 +17,7 @@ import { WEBDAV_SSL_CERTS_DIR, WEBDAV_DEFAULT_DELETE_FILES_PERMANENTLY, } from '../constants/configs'; +import { CacheService } from './cache.service'; export class ConfigService { public static readonly instance: ConfigService = new ConfigService(); @@ -44,6 +45,8 @@ export class ConfigService { const credentialsString = JSON.stringify(loginCredentials); const encryptedCredentials = CryptoService.instance.encryptText(credentialsString); await fs.writeFile(CREDENTIALS_FILE, encryptedCredentials, 'utf8'); + + CacheService.instance.set(CacheService.AUTH_CACHE_KEY, loginCredentials); }; /** @@ -51,6 +54,7 @@ export class ConfigService { * @async **/ public clearUser = async (): Promise => { + CacheService.instance.set(CacheService.AUTH_CACHE_KEY, undefined); try { const stat = await fs.stat(CREDENTIALS_FILE); if (stat.size === 0) return; @@ -68,6 +72,9 @@ export class ConfigService { * @async **/ public readUser = async (): Promise => { + const cached = CacheService.instance.get(CacheService.AUTH_CACHE_KEY); + if (cached) return cached; + try { const encryptedCredentials = await fs.readFile(CREDENTIALS_FILE, 'utf8'); const credentialsString = CryptoService.instance.decryptText(encryptedCredentials); diff --git a/test/services/config.service.test.ts b/test/services/config.service.test.ts index 9b20953b..48af710a 100644 --- a/test/services/config.service.test.ts +++ b/test/services/config.service.test.ts @@ -19,6 +19,7 @@ import { WEBDAV_DEFAULT_DELETE_FILES_PERMANENTLY, } from '../../src/constants/configs'; import { getWebdavConfigMock } from '../fixtures/webdav.fixture'; +import { CacheService } from '../../src/services/cache.service'; const env = Object.assign({}, process.env); @@ -37,6 +38,8 @@ describe('Config service', () => { beforeEach(() => { process.env = env; + vi.spyOn(CacheService.instance, 'get').mockReturnValue(null); + vi.spyOn(CacheService.instance, 'set').mockImplementation(() => {}); }); it('When an env property is requested, then the get method return its value', async () => { From beb050bd3c03b66a418c9ec87ddb2bd6c8b5dc08 Mon Sep 17 00:00:00 2001 From: larryrider Date: Thu, 4 Jun 2026 16:48:22 +0200 Subject: [PATCH 10/11] chore: bump version to 1.6.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 463a2662..30f6b72e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "author": "Internxt ", - "version": "1.6.4", + "version": "1.6.5", "description": "Internxt CLI to manage your encrypted storage", "scripts": { "build": "yarn clean && tsc", From b9f48050c2d854858cb24acf721403a687f693c7 Mon Sep 17 00:00:00 2001 From: larryrider Date: Thu, 4 Jun 2026 17:32:35 +0200 Subject: [PATCH 11/11] fix: change response status for existing folder in MKCOL handler to 200 --- src/webdav/handlers/MKCOL.handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webdav/handlers/MKCOL.handler.ts b/src/webdav/handlers/MKCOL.handler.ts index 931f605f..0f4cd776 100644 --- a/src/webdav/handlers/MKCOL.handler.ts +++ b/src/webdav/handlers/MKCOL.handler.ts @@ -22,7 +22,7 @@ export class MKCOLRequestHandler implements WebDavMethodHandler { if (folderAlreadyExists) { webdavLogger.info(`[MKCOL] Folder '${resource.url}' already exists, ignoring the creation request`); - res.status(201).send(XMLUtils.toWebDavXML({}, {})); + res.status(200).send(XMLUtils.toWebDavXML({}, {})); return; }