Skip to content

Commit

Permalink
change services to class
Browse files Browse the repository at this point in the history
  • Loading branch information
larryrider committed Feb 12, 2024
1 parent fc17e9a commit 5390403
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 175 deletions.
24 changes: 12 additions & 12 deletions src/services/SDKManager.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,38 +18,38 @@ export class SdkManager {
* Sets the security details needed to create SDK clients
* @param apiSecurity Security properties to be setted
*/
static init(apiSecurity: SdkManagerApiSecurity) {
static init = (apiSecurity: SdkManagerApiSecurity) => {
SdkManager.setApiSecurity(apiSecurity);
}
};

static setApiSecurity(apiSecurity: SdkManagerApiSecurity) {
static setApiSecurity = (apiSecurity: SdkManagerApiSecurity) => {
SdkManager.apiSecurity = apiSecurity;
}
};

static clean() {
static clean = () => {
SdkManager.apiSecurity = undefined;
}
};

static getInstance() {
static getInstance = () => {
if (!SdkManager.instance) {
throw new Error('No instance found, call init method first');
}
return SdkManager.instance;
}
};

public getApiSecurity(config = { throwErrorOnMissingCredentials: true }): SdkManagerApiSecurity {
public getApiSecurity = (config = { throwErrorOnMissingCredentials: true }): SdkManagerApiSecurity => {
if (!SdkManager.apiSecurity && config.throwErrorOnMissingCredentials)
throw new Error('Api security properties not found in SdkManager');

return SdkManager.apiSecurity as SdkManagerApiSecurity;
}
};

private static getAppDetails(): AppDetails {
private static getAppDetails = (): AppDetails => {
return {
clientName: packageJson.name,
clientVersion: packageJson.version,
};
}
};

/** Auth SDK */
get authV2() {
Expand Down
124 changes: 55 additions & 69 deletions src/services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,61 @@
import { CryptoProvider, LoginDetails } from '@internxt/sdk';
import { Keys, Password } from '@internxt/sdk/dist/auth';
import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings.js';
import { aes } from '@internxt/lib';
import { SdkManager } from './SDKManager.service';
import { KeysService } from './keys.service';
import { OpenpgpService } from './pgp.service';
import { decryptText, decryptTextWithKey, encryptText, passToHash } from '../utils/crypto.utils';
import { generateNewKeys } from './pgp.service';
import { assertPrivateKeyIsValid, assertValidateKeys, decryptPrivateKey, getAesInitFromEnv } from './keys.service';

const generateNewKeysWithEncrypted = async (password: string) => {
const { privateKeyArmored, publicKeyArmored, revocationCertificate } = await generateNewKeys();
export class AuthService {
public static readonly instance: AuthService = new AuthService();

return {
privateKeyArmored,
privateKeyArmoredEncrypted: aes.encrypt(privateKeyArmored, password, getAesInitFromEnv()),
publicKeyArmored,
revocationCertificate,
};
};

export const is2FANeeded = async (email: string): Promise<boolean> => {
const authClient = SdkManager.getInstance().auth;
const securityDetails = await authClient.securityDetails(email).catch((error) => {
throw new Error(error.message ?? 'Login error');
});
return securityDetails.tfaEnabled;
};

export const doLogin = async (
email: string,
password: string,
twoFactorCode: string,
): Promise<{
user: UserSettings;
token: string;
newToken: string;
mnemonic: string;
}> => {
const authClient = SdkManager.getInstance().auth;
const loginDetails: LoginDetails = {
email: email.toLowerCase(),
password: password,
tfaCode: twoFactorCode,
};
const cryptoProvider: CryptoProvider = {
encryptPasswordHash(password: Password, encryptedSalt: string): string {
const salt = decryptText(encryptedSalt);
const hashObj = passToHash({ password, salt });
return encryptText(hashObj.hash);
},
async generateKeys(password: Password): Promise<Keys> {
const { privateKeyArmoredEncrypted, publicKeyArmored, revocationCertificate } =
await generateNewKeysWithEncrypted(password);
const keys: Keys = {
privateKeyEncrypted: privateKeyArmoredEncrypted,
publicKey: publicKeyArmored,
revocationCertificate: revocationCertificate,
};
return keys;
},
};
public doLogin = async (
email: string,
password: string,
twoFactorCode: string,
): Promise<{
user: UserSettings;
token: string;
newToken: string;
mnemonic: string;
}> => {
const authClient = SdkManager.getInstance().auth;
const loginDetails: LoginDetails = {
email: email.toLowerCase(),
password: password,
tfaCode: twoFactorCode,
};
const cryptoProvider: CryptoProvider = {
encryptPasswordHash(password: Password, encryptedSalt: string): string {
const salt = decryptText(encryptedSalt);
const hashObj = passToHash({ password, salt });
return encryptText(hashObj.hash);
},
async generateKeys(password: Password): Promise<Keys> {
const { privateKeyArmoredEncrypted, publicKeyArmored, revocationCertificate } =
await OpenpgpService.instance.generateNewKeysWithEncrypted(password);
const keys: Keys = {
privateKeyEncrypted: privateKeyArmoredEncrypted,
publicKey: publicKeyArmored,
revocationCertificate: revocationCertificate,
};
return keys;
},
};

return authClient
.login(loginDetails, cryptoProvider)
.then(async (data) => {
// eslint-disable-next-line no-useless-catch
try {
const data = await authClient.login(loginDetails, cryptoProvider);
const { user, token, newToken } = data;
const { privateKey, publicKey } = user;

const plainPrivateKeyInBase64 = privateKey
? Buffer.from(decryptPrivateKey(privateKey, password)).toString('base64')
? Buffer.from(KeysService.instance.decryptPrivateKey(privateKey, password)).toString('base64')
: '';

if (privateKey) {
await assertPrivateKeyIsValid(privateKey, password);
await assertValidateKeys(
await KeysService.instance.assertPrivateKeyIsValid(privateKey, password);
await KeysService.instance.assertValidateKeys(
Buffer.from(plainPrivateKeyInBase64, 'base64').toString(),
Buffer.from(publicKey, 'base64').toString(),
);
Expand All @@ -84,20 +67,23 @@ export const doLogin = async (
mnemonic: clearMnemonic,
privateKey: plainPrivateKeyInBase64,
};

//TODO save tokens for later use
/*localStorageService.set('xToken', token);
localStorageService.set('xMnemonic', clearMnemonic);
localStorageService.set('xNewToken', newToken);*/

return {
user: clearUser,
token: token,
newToken: newToken,
mnemonic: clearMnemonic,
};
})
.catch((error) => {
} catch (error) {
//TODO send Sentry login errors and remove eslint-disable from this trycatch
throw error;
}
};

public is2FANeeded = async (email: string): Promise<boolean> => {
const authClient = SdkManager.getInstance().auth;
const securityDetails = await authClient.securityDetails(email).catch((error) => {
throw new Error(error.message ?? 'Login error');
});
};
return securityDetails.tfaEnabled;
};
}
4 changes: 2 additions & 2 deletions src/services/config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { ConfigKeys } from '../types/config.types';
export class ConfigService {
public static readonly instance: ConfigService = new ConfigService();

get(key: keyof ConfigKeys): string {
public get = (key: keyof ConfigKeys): string => {
const value = process.env[key];
if (!value) throw new Error(`Config key ${key} was not found in process.env`);
return value;
}
};
}
112 changes: 54 additions & 58 deletions src/services/keys.service.ts
Original file line number Diff line number Diff line change
@@ -1,99 +1,95 @@
import { aes } from '@internxt/lib';
import { isValid } from '../utils/pgp.utils';
import { getOpenpgp } from './pgp.service';
import { isValidKey } from '../utils/pgp.utils';
import { ConfigService } from './config.service';
import { OpenpgpService } from './pgp.service';

export class Base64EncodedPrivateKeyError extends Error {
class Base64EncodedPrivateKeyError extends Error {
constructor() {
super('Key is encoded in base64');

Object.setPrototypeOf(this, Base64EncodedPrivateKeyError.prototype);
}
}

export class WrongIterationsToEncryptPrivateKeyError extends Error {
class WrongIterationsToEncryptPrivateKeyError extends Error {
constructor() {
super('Key was encrypted using the wrong iterations number');

Object.setPrototypeOf(this, WrongIterationsToEncryptPrivateKeyError.prototype);
}
}

export class CorruptedEncryptedPrivateKeyError extends Error {
class CorruptedEncryptedPrivateKeyError extends Error {
constructor() {
super('Key is corrupted');

Object.setPrototypeOf(this, CorruptedEncryptedPrivateKeyError.prototype);
}
}

export class KeysDoNotMatchError extends Error {
class KeysDoNotMatchError extends Error {
constructor() {
super('Keys do not match');

Object.setPrototypeOf(this, CorruptedEncryptedPrivateKeyError.prototype);
}
}

/**
* This function validates the private key
* @param privateKey The private key to validate encrypted
* @param password The password used for encrypting the private key
* @throws {Base64EncodedPrivateKeyError} If the PLAIN private key is base64 encoded (known issue introduced in the past)
* @throws {WrongIterationsToEncryptPrivateKeyError} If the ENCRYPTED private key was encrypted using the wrong iterations number (known issue introduced in the past)
* @throws {CorruptedEncryptedPrivateKeyError} If the ENCRYPTED private key is un-decryptable (corrupted)
* @async
*/
export async function assertPrivateKeyIsValid(privateKey: string, password: string): Promise<void> {
let privateKeyDecrypted: string;

try {
privateKeyDecrypted = decryptPrivateKey(privateKey, password);
} catch {
export class KeysService {
public static readonly instance: KeysService = new KeysService();

public assertPrivateKeyIsValid = async (privateKey: string, password: string): Promise<void> => {
let privateKeyDecrypted: string;

try {
aes.decrypt(privateKey, password, 9999);
privateKeyDecrypted = this.decryptPrivateKey(privateKey, password);
} catch {
throw new CorruptedEncryptedPrivateKeyError();
try {
aes.decrypt(privateKey, password, 9999);
} catch {
throw new CorruptedEncryptedPrivateKeyError();
}

throw new WrongIterationsToEncryptPrivateKeyError();
}

throw new WrongIterationsToEncryptPrivateKeyError();
}
const hasValidFormat = await isValidKey(privateKeyDecrypted);

const hasValidFormat = await isValid(privateKeyDecrypted);
if (!hasValidFormat) throw new Base64EncodedPrivateKeyError();
};

if (!hasValidFormat) throw new Base64EncodedPrivateKeyError();
}
public decryptPrivateKey = (privateKey: string, password: string): string => {
return aes.decrypt(privateKey, password);
};

export function decryptPrivateKey(privateKey: string, password: string): string {
return aes.decrypt(privateKey, password);
}
public assertValidateKeys = async (privateKey: string, publicKey: string): Promise<void> => {
const publicKeyArmored = await OpenpgpService.openpgp.readKey({ armoredKey: publicKey });
const privateKeyArmored = await OpenpgpService.openpgp.readPrivateKey({ armoredKey: privateKey });

export async function assertValidateKeys(privateKey: string, publicKey: string): Promise<void> {
const openpgp = await getOpenpgp();
const publicKeyArmored = await openpgp.readKey({ armoredKey: publicKey });
const privateKeyArmored = await openpgp.readPrivateKey({ armoredKey: privateKey });

const plainMessage = 'validate-keys';
const originalText = await openpgp.createMessage({ text: plainMessage });
const encryptedMessage = await openpgp.encrypt({
message: originalText,
encryptionKeys: publicKeyArmored,
});

const decryptedMessage = (
await openpgp.decrypt({
message: await openpgp.readMessage({ armoredMessage: encryptedMessage }),
verificationKeys: publicKeyArmored,
decryptionKeys: privateKeyArmored,
})
).data;

if (decryptedMessage !== plainMessage) {
throw new KeysDoNotMatchError();
}
}
const plainMessage = 'validate-keys';
const originalText = await OpenpgpService.openpgp.createMessage({ text: plainMessage });
const encryptedMessage = await OpenpgpService.openpgp.encrypt({
message: originalText,
encryptionKeys: publicKeyArmored,
});

const decryptedMessage = (
await OpenpgpService.openpgp.decrypt({
message: await OpenpgpService.openpgp.readMessage({ armoredMessage: encryptedMessage }),
verificationKeys: publicKeyArmored,
decryptionKeys: privateKeyArmored,
})
).data;

if (decryptedMessage !== plainMessage) {
throw new KeysDoNotMatchError();
}
};

export function getAesInitFromEnv(): { iv: string; salt: string } {
const { REACT_APP_MAGIC_IV: MAGIC_IV, REACT_APP_MAGIC_SALT: MAGIC_SALT } = process.env;
public getAesInitFromEnv = (): { iv: string; salt: string } => {
const MAGIC_IV = ConfigService.instance.get('REACT_APP_MAGIC_IV');
const MAGIC_SALT = ConfigService.instance.get('REACT_APP_MAGIC_SALT');

return { iv: MAGIC_IV as string, salt: MAGIC_SALT as string };
return { iv: MAGIC_IV as string, salt: MAGIC_SALT as string };
};
}
Loading

0 comments on commit 5390403

Please sign in to comment.