Skip to content
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7909f63
add opaque
TamaraFinogina Oct 14, 2025
496bc7a
Merge remote-tracking branch 'origin/master' into opaque_poc
TamaraFinogina Oct 14, 2025
1839209
add disble 2FA
TamaraFinogina Oct 15, 2025
9489a85
add password change, add login after signup
TamaraFinogina Oct 17, 2025
6a1dcf1
Merge remote-tracking branch 'origin/master' into opaque_poc
TamaraFinogina Oct 17, 2025
628aa90
remove extra changes
TamaraFinogina Oct 17, 2025
04077cc
add tests for opaque versions of signUp, login, disable2FA, and chan…
TamaraFinogina Oct 23, 2025
44db502
add new files
TamaraFinogina Oct 23, 2025
1bc9d59
reduce changes in yarn.lock
TamaraFinogina Oct 23, 2025
bf329f2
reduce changes in types
TamaraFinogina Oct 23, 2025
916fc9c
upgrade internxt-crypto
TamaraFinogina Oct 24, 2025
74868ca
put is2FAorOpaqueNeeded separately
TamaraFinogina Oct 24, 2025
76922ee
remove 2FA example
TamaraFinogina Oct 24, 2025
10cefd0
fix PR comments
TamaraFinogina Oct 24, 2025
c65e17c
fix test description
TamaraFinogina Oct 27, 2025
080b94e
don't use localStorage for storing values between tests
TamaraFinogina Oct 27, 2025
872dd0c
add more tests
TamaraFinogina Oct 27, 2025
c974d3f
Merge remote-tracking branch 'origin/master' into opaque_poc
TamaraFinogina Oct 27, 2025
945e306
fix sonar errors
TamaraFinogina Oct 27, 2025
5d32592
fix _ in crypto test
TamaraFinogina Oct 27, 2025
3fee78b
fix comments
TamaraFinogina Oct 28, 2025
e1223b0
Merge branch 'master' into opaque_poc
CandelR Nov 3, 2025
0ab63be
Merge remote-tracking branch 'origin/master' into opaque_poc
TamaraFinogina Nov 4, 2025
4fba4dd
Merge branch 'master' into opaque_poc
CandelR Nov 5, 2025
ee02788
Merge branch 'master' into opaque_poc
CandelR Nov 7, 2025
739f848
Merge remote-tracking branch 'origin/master' into opaque_poc
TamaraFinogina Nov 14, 2025
994b85e
update sdk, update internext-crypto
TamaraFinogina Nov 21, 2025
5d0350e
Merge remote-tracking branch 'origin/master' into opaque_poc
TamaraFinogina Nov 21, 2025
8bcdaa8
yarn.lock changes
TamaraFinogina Nov 21, 2025
b9be060
increase node version
TamaraFinogina Nov 21, 2025
b97e446
use node 20
TamaraFinogina Nov 26, 2025
e54ff76
fix crypto package link
TamaraFinogina Nov 26, 2025
c1dda59
fix pathes
TamaraFinogina Nov 26, 2025
cac4356
merge master
TamaraFinogina Nov 26, 2025
baf3538
address PR comments
TamaraFinogina Dec 5, 2025
7b8bca5
merge master
TamaraFinogina Dec 5, 2025
8a70388
Merge remote-tracking branch 'origin/master' into opaque_poc
TamaraFinogina Dec 15, 2025
435b604
remove diasable 2FA
TamaraFinogina Dec 15, 2025
19f663a
Merge remote-tracking branch 'origin/master' into opaque_poc
TamaraFinogina Dec 18, 2025
6a35e91
update sdk and api
TamaraFinogina Jan 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@reduxjs/toolkit": "^1.6.0",
"@sentry/react": "^8.55.0",
"@sentry/tracing": "^7.120.3",
"@serenity-kit/opaque": "^0.9.0",
"@stripe/react-stripe-js": "^2.7.1",
"@stripe/stripe-js": "^3.5.0",
"@typeform/embed-react": "^1.19.0",
Expand All @@ -39,6 +40,7 @@
"i18next": "^22.4.9",
"i18next-browser-languagedetector": "^7.2.0",
"idb": "^6.1.5",
"internxt-crypto": "0.0.8-alpha",
"js-file-download": "^0.4.12",
"lint-staged": "^13.1.0",
"lodash": "^4.17.21",
Expand Down
7 changes: 4 additions & 3 deletions src/app/auth/components/LogIn/LogIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { twoFactorRegexPattern } from 'app/core/services/validation.service';
import { RootState } from 'app/store';
import { useAppDispatch } from 'app/store/hooks';
import { userActions } from 'app/store/slices/user';
import authService, { authenticateUser, is2FANeeded } from '../../services/auth.service';
import authService, { authenticateUser } from '../../services/auth.service';

import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings';
import { WarningCircle } from '@phosphor-icons/react';
Expand All @@ -29,6 +29,7 @@ import TextInput from '../TextInput/TextInput';
import { AuthMethodTypes } from 'app/payment/types';
import vpnAuthService from 'app/auth/services/vpnAuth.service';
import envService from 'app/core/services/env.service';
import { logInOpaque, is2FAorOpaqueNeeded } from '../../services/auth.opaque';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in the auth service, let's not create 'helper' files

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


const showNotification = ({ text, isError }: { text: string; isError: boolean }) => {
notificationsService.show({
Expand Down Expand Up @@ -168,7 +169,7 @@ export default function LogIn(): JSX.Element {
const { email, password } = formData;

try {
const isTfaEnabled = await is2FANeeded(email);
const { tfaEnabled: isTfaEnabled, opaqueLogin } = await is2FAorOpaqueNeeded(email);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we hide these things under the same functions? Otherwise we mix component-related logic with authentication low-level details, which is far from ideal in terms of maintenance and testability. Let's hide these things under the auth layer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


if (!isTfaEnabled || showTwoFactor) {
const loginType: 'desktop' | 'web' = isUniversalLinkMode ? 'desktop' : 'web';
Expand All @@ -181,7 +182,7 @@ export default function LogIn(): JSX.Element {
loginType,
};

const { token, user, mnemonic } = await authenticateUser(authParams);
const { token, user, mnemonic } = await (opaqueLogin ? logInOpaque(authParams) : authenticateUser(authParams));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

handleSuccessfulAuth(token, user, mnemonic);
} else {
setShowTwoFactor(true);
Expand Down
20 changes: 20 additions & 0 deletions src/app/auth/services/auth.crypto.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest';
import { generateUserSecrets, encryptUserKeysAndMnemonic, decryptUserKeysAndMnemonic } from './auth.crypto';

describe('Test auth crypto functions', () => {
it('test enc/dec of user sercrets', async () => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use When-then legends and define clearly what each case is doing / testing

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

const { keys, mnemonic } = await generateUserSecrets();
const exportKey = 'Srp6AzybbyludWuaVwGoHa1C2H0Qtv7JR0sKGLSWe8Ho8_q9hezfYD2RYb9IUrW999pH4VlABgDLse484zAapg';

const { encMnemonic, encKeys } = await encryptUserKeysAndMnemonic(keys, mnemonic, exportKey);

const { keys: dec_keys, mnemonic: dec_mnemonic } = await decryptUserKeysAndMnemonic(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid using snake_case, the convention is camelCase

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

encMnemonic,
encKeys,
exportKey,
);

expect(keys).toStrictEqual(dec_keys);
expect(mnemonic).toEqual(dec_mnemonic);
});
});
92 changes: 92 additions & 0 deletions src/app/auth/services/auth.crypto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { symmetric, utils, deriveKey } from 'internxt-crypto';
import { UserKeys } from '@internxt/sdk';

import { generateNewKeys } from 'app/crypto/services/pgp.service';
export async function encryptUserKeysAndMnemonic(
userKeys: UserKeys,
mnemonic: string,
exportKey: string,
): Promise<{ encMnemonic: string; encKeys: UserKeys }> {
const cryptoKey = await symmetric.deriveSymmetricCryptoKey(exportKey);
const key = utils.UTF8ToUint8(userKeys.ecc.privateKey);
const encPrivateKey = await symmetric.encryptSymmetrically(cryptoKey, key, 'user-private-key');
const keyKyber = utils.base64ToUint8Array(userKeys.kyber.privateKey);
const encPrivateKyberKey = await symmetric.encryptSymmetrically(cryptoKey, keyKyber, 'user-private-kyber-key');
const mnemonicArray = utils.UTF8ToUint8(mnemonic);
const mnemonicCipher = await symmetric.encryptSymmetrically(cryptoKey, mnemonicArray, 'user-mnemonic');
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract the constants to a constants file to reuse it in the different necessary points

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

const encMnemonic = utils.ciphertextToBase64(mnemonicCipher);

const encKeys: UserKeys = {
ecc: {
privateKey: utils.ciphertextToBase64(encPrivateKey),
publicKey: userKeys.ecc.publicKey,
},
kyber: {
privateKey: utils.ciphertextToBase64(encPrivateKyberKey),
publicKey: userKeys.kyber.publicKey,
},
};
return { encMnemonic, encKeys };
}

export async function decryptUserKeysAndMnemonic(
encMnemonic: string,
encKeys: UserKeys,
exportKey: string,
): Promise<{ keys: UserKeys; mnemonic: string }> {
const cryptoKey = await symmetric.deriveSymmetricCryptoKey(exportKey);
const encKey = utils.base64ToCiphertext(encKeys.ecc.privateKey);
const privateKey = await symmetric.decryptSymmetrically(cryptoKey, encKey, 'user-private-key');
const encKyberKey = utils.base64ToCiphertext(encKeys.kyber.privateKey);
const privateKyberKey = await symmetric.decryptSymmetrically(cryptoKey, encKyberKey, 'user-private-kyber-key');
const encMnemonicArray = utils.base64ToCiphertext(encMnemonic);
const mnemonicArray = await symmetric.decryptSymmetrically(cryptoKey, encMnemonicArray, 'user-mnemonic');
const mnemonic = utils.uint8ToUTF8(mnemonicArray);

const keys: UserKeys = {
ecc: {
privateKey: utils.uint8ToUTF8(privateKey),
publicKey: encKeys.ecc.publicKey,
},
kyber: {
privateKey: utils.uint8ArrayToBase64(privateKyberKey),
publicKey: encKeys.kyber.publicKey,
},
};
return { keys, mnemonic };
}

export const encryptSessionKey = async (
password: string,
sessionKey: string,
): Promise<{ sessionKeyEnc: string; salt: string }> => {
const { key, salt } = await deriveKey.getKeyFromPassword(password);
const cryptoKey = await symmetric.importSymmetricCryptoKey(key);
const sessionKeyArray = utils.base64ToUint8Array(sessionKey);
const sessionKeyEncCipher = await symmetric.encryptSymmetrically(cryptoKey, sessionKeyArray, 'UserSessionKey');
const sessionKeyEnc = utils.ciphertextToBase64(sessionKeyEncCipher);
return { sessionKeyEnc, salt };
};

export const decryptSessionKey = async (password: string, sessionKeyEnc: string, salt: string): Promise<string> => {
const keyBytes = await deriveKey.getKeyFromPasswordAndSalt(password, salt);
const key = await symmetric.importSymmetricCryptoKey(keyBytes);
const sessionKeyCipher = utils.base64ToCiphertext(sessionKeyEnc);
const sessionKeyArray = await symmetric.decryptSymmetrically(key, sessionKeyCipher, 'UserSessionKey');
const sessionKey = utils.uint8ArrayToBase64(sessionKeyArray);
const urlSafeSessionKey = sessionKey.replace(/\+/g, '-').replace(/\//g, '_').replace(/=$/, '').replace(/=$/, '');
return urlSafeSessionKey;
};

export const generateUserSecrets = async (): Promise<{ keys: UserKeys; mnemonic: string }> => {
const mnemonic = utils.genMnemonic(256);
const { privateKeyArmored, publicKeyArmored, publicKyberKeyBase64, privateKyberKeyBase64 } = await generateNewKeys();
const keys: UserKeys = {
ecc: { privateKey: privateKeyArmored, publicKey: publicKeyArmored },
kyber: {
privateKey: privateKyberKeyBase64,
publicKey: publicKyberKeyBase64,
},
};
return { keys, mnemonic };
};
Loading
Loading