From 7f13967229892a4ee8677c53b5ce3ea656fcf285 Mon Sep 17 00:00:00 2001 From: Andres Pinto <143480783+apsantiso@users.noreply.github.com> Date: Mon, 21 Sep 2026 14:44:31 +0200 Subject: [PATCH] feat(login): allow skipping security details if they're already known --- package.json | 2 +- src/auth/index.ts | 37 +++++++++++++------------- test/auth/index.test.ts | 57 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 7e0c2849..e0cce7b2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@internxt/sdk", "author": "Internxt ", - "version": "1.20.2", + "version": "1.21.0", "description": "An sdk for interacting with Internxt's services", "repository": { "type": "git", diff --git a/src/auth/index.ts b/src/auth/index.ts index 1a9c7c7b..a33e5fad 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -189,17 +189,19 @@ export class Auth { * Tries to log in a user given its login details * @param details * @param cryptoProvider + * @param knownSecurityDetails Skips the internal `/auth/login` call if the caller already has it */ public async login( details: LoginDetails, cryptoProvider: CryptoProvider, + knownSecurityDetails?: SecurityDetails, ): Promise<{ token: Token; newToken: Token; user: UserSettings; userTeam: TeamsSettings | null; }> { - const securityDetails = await this.securityDetails(details.email); + const securityDetails = knownSecurityDetails ?? (await this.securityDetails(details.email)); const encryptedSalt = securityDetails.encryptedSalt; const encryptedPasswordHash = cryptoProvider.encryptPasswordHash(details.password, encryptedSalt); const keys = await cryptoProvider.generateKeys(details.password); @@ -325,24 +327,21 @@ export class Auth { * Returns general security details * @param email */ - public securityDetails(email: string): Promise { - return this.client - .post<{ - sKey: string; - tfa: boolean | null; - }>( - '/auth/login', - { - email: email, - }, - this.basicHeaders(), - ) - .then((data) => { - return { - encryptedSalt: data.sKey, - tfaEnabled: data.tfa === true, - }; - }); + public async securityDetails(email: string): Promise { + const data = await this.client.post<{ + sKey: string; + tfa: boolean | null; + }>( + '/auth/login', + { + email: email, + }, + this.basicHeaders(), + ); + return { + encryptedSalt: data.sKey, + tfaEnabled: data.tfa === true, + }; } /** diff --git a/test/auth/index.test.ts b/test/auth/index.test.ts index d0e81b5d..684ca5a0 100644 --- a/test/auth/index.test.ts +++ b/test/auth/index.test.ts @@ -411,6 +411,63 @@ describe('# auth service tests', () => { headers, ); }); + + it('Should skip the securityDetails call when knownSecurityDetails is provided', async () => { + // Arrange + const { client, headers } = clientAndHeaders(); + const loginDetails: LoginDetails = { + email: 'my_email', + password: 'password', + tfaCode: undefined, + }; + const cryptoProvider: CryptoProvider = { + encryptPasswordHash: (password, encryptedSalt) => password + '-' + encryptedSalt, + generateKeys: () => { + const keys: Keys = { + ecc: { + publicKey: 'pub', + privateKeyEncrypted: 'priv', + }, + kyber: { + publicKey: 'pubKyber', + privateKeyEncrypted: 'privKyber', + }, + }; + return Promise.resolve(keys); + }, + }; + const postStub = vi.spyOn(HttpClient.prototype, 'post').mockResolvedValueOnce({ + user: {}, + }); + + // Act + await client.login(loginDetails, cryptoProvider, { + encryptedSalt: 'known_encrypted_salt', + tfaEnabled: false, + }); + + // Assert + expect(postStub).toHaveBeenCalledTimes(1); + expect(postStub).toHaveBeenCalledWith( + '/auth/login/access', + { + email: loginDetails.email, + password: 'password-known_encrypted_salt', + tfa: loginDetails.tfaCode, + keys: { + ecc: { + publicKey: 'pub', + privateKey: 'priv', + }, + kyber: { + publicKey: 'pubKyber', + privateKey: 'privKyber', + }, + }, + }, + headers, + ); + }); }); describe('-> loginWithoutKeys use case', () => {