Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 14 additions & 16 deletions src/chat/ChatProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { createContext, useEffect, useReducer } from 'react';
import React, { createContext, PropsWithChildren, useEffect, useReducer } from 'react';
import { FirestoreServices, createUserProfile } from '../services/firebase';
import type { IChatContext } from '../interfaces';
import {
Expand All @@ -9,22 +9,17 @@ import {

const firestoreServices = FirestoreServices.getInstance();

interface ChatProviderProps
extends Omit<IChatContext, 'chatState' | 'chatDispatch'> {
children?: React.ReactNode;
}
type ChatProviderProps = IChatContext & PropsWithChildren;

export const ChatContext = createContext<IChatContext>({} as IChatContext);
export const ChatProvider: React.FC<ChatProviderProps> = ({
userInfo,
children,
enableEncrypt = false,
encryptKey = '',
blackListWords,
encryptionOptions,
encryptionFuncProps,
prefix = '',
CustomImageComponent,
...props
}) => {
const [state, dispatch] = useReducer(chatReducer, {});

Expand All @@ -51,12 +46,14 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
useEffect(() => {
encryptionFuncProps &&
firestoreServices.createEncryptionsFunction(encryptionFuncProps);
firestoreServices.configuration({
encryptKey,
enableEncrypt,
encryptionOptions,
});
}, [encryptKey, enableEncrypt, encryptionOptions, encryptionFuncProps]);
if (props.enableEncrypt && props.encryptKey) {
firestoreServices.configurationEncryption({
encryptKey: props.encryptKey,
enableEncrypt: props.enableEncrypt,
encryptionOptions: props.encryptionOptions,
});
}
}, [props, encryptionFuncProps]);

useEffect(() => {
firestoreServices.configuration({ blackListWords });
Expand All @@ -70,10 +67,11 @@ export const ChatProvider: React.FC<ChatProviderProps> = ({
<ChatContext.Provider
value={{
userInfo,
chatState: state,
chatDispatch: dispatch,
blackListWords,
CustomImageComponent,
...props,
chatState: state,
chatDispatch: dispatch,
}}
>
{children}
Expand Down
3 changes: 0 additions & 3 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
// constants.ts

import { generateRandomUUID } from './utilities';

export const DEFAULT_SALT: string = generateRandomUUID(); // Generate a unique salt
export const DEFAULT_ITERATIONS: number = 10000; // Increased for better security
export const DEFAULT_KEY_LENGTH: number = 256;
export const DEFAULT_CLEAR_SEND_NOTIFICATION: number = 3000;
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/AESCrypto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface EncryptionOptions {
salt?: string;
salt: string;
iterations?: number;
keyLength?: number;
}
Expand Down
14 changes: 3 additions & 11 deletions src/interfaces/chat.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import type { ChatAction, ChatState } from '../reducer/chat';
import type { Dispatch } from 'react';
import { EncryptionFunctions, EncryptionOptions } from './AESCrypto';
import type { FirestoreProps } from '../services/firebase';
import { ImageProps } from 'react-native';

export interface IUserInfo {
id: string;
name: string;
avatar: string;
}

export interface IChatContext {
userInfo: IUserInfo | null;
enableEncrypt?: boolean;
blackListWords?: string[];
encryptionOptions?: EncryptionOptions;
encryptionFuncProps?: EncryptionFunctions;
encryptKey?: string;
prefix?: string;
export type IChatContext = {
chatState: ChatState;
chatDispatch: Dispatch<ChatAction>;
CustomImageComponent?: React.ComponentType<ImageProps>;
}
} & FirestoreProps
83 changes: 50 additions & 33 deletions src/services/firebase/firestore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,34 @@ import {
} from '../../interfaces';
import { uploadFileToFirebase } from './storage';

interface FirestoreProps {
userInfo?: IUserInfo;
enableEncrypt?: boolean;
type PropsWithEncryption = {
enableEncrypt: true;
encryptionOptions: EncryptionOptions;
encryptKey?: string;
}

type PropsWithoutEncryption = {
enableEncrypt: false;
}

type FirestoreEncryptionProps = PropsWithEncryption | PropsWithoutEncryption;

type FirestoreBaseProps = {
userInfo?: IUserInfo;
memberIds?: string[];
blackListWords?: string[];
encryptionOptions?: EncryptionOptions;
encryptionFuncProps?: EncryptionFunctions;
prefix?: string;
}

export type FirestoreProps = FirestoreBaseProps & FirestoreEncryptionProps;

export class FirestoreServices {
private static instance: FirestoreServices;

/** User configuration */
userInfo: IUserInfo | undefined;
enableEncrypt: boolean | undefined;
enableEncrypt: boolean = false;
encryptKey: string = '';
regexBlacklist: RegExp | undefined;
prefix = '';
Expand Down Expand Up @@ -101,12 +112,9 @@ export class FirestoreServices {

configuration = async ({
userInfo,
enableEncrypt,
encryptKey,
blackListWords,
encryptionOptions,
prefix,
}: FirestoreProps): Promise<void> => {
}: FirestoreBaseProps): Promise<void> => {
// Validate user info
if (userInfo) {
if (!validateUserId(userInfo.id)) {
Expand All @@ -124,34 +132,43 @@ export class FirestoreServices {
this.regexBlacklist = generateBadWordsRegex(blackListWords);
}

if (enableEncrypt && encryptKey) {
try {
// Validate encryption key strength
const keyValidation = validateEncryptionKey(encryptKey);
if (!keyValidation.isValid) {
console.warn('Weak encryption key detected:', keyValidation.errors);
// Continue but log warnings - don't break existing functionality
}

this.enableEncrypt = enableEncrypt;
this.encryptKey = this.generateKeyFunctionProp
? await this.generateKeyFunctionProp(encryptKey)
: await generateEncryptionKey(encryptKey, encryptionOptions);
if (prefix) {
this.prefix = prefix;
}
};

configurationEncryption = async ({
encryptKey,
encryptionOptions,
}: PropsWithEncryption) => {
if (!encryptKey) {
console.error('Encrypt key is required');
return;
}

// Validate the generated key
if (!this.encryptKey || this.encryptKey.length === 0) {
throw new Error('Failed to generate encryption key');
}
} catch (error) {
console.error('Error configuring encryption:', error);
this.enableEncrypt = false;
this.encryptKey = '';
throw new Error('Failed to configure encryption');
try {
// Validate encryption key strength
const keyValidation = validateEncryptionKey(encryptKey);
if (!keyValidation.isValid) {
console.warn('Weak encryption key detected:', keyValidation.errors);
// Continue but log warnings - don't break existing functionality
}
}

if (prefix) {
this.prefix = prefix;
this.enableEncrypt = true;
this.encryptKey = this.generateKeyFunctionProp
? await this.generateKeyFunctionProp(encryptKey)
: await generateEncryptionKey(encryptKey, encryptionOptions);

// Validate the generated key
if (!this.encryptKey || this.encryptKey.length === 0) {
throw new Error('Failed to generate encryption key');
}
} catch (error) {
console.error('Error configuring encryption:', error);
this.enableEncrypt = false;
this.encryptKey = '';
throw new Error('Failed to configure encryption');
}
};

Expand Down
7 changes: 3 additions & 4 deletions src/utilities/aesCrypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import Aes from 'react-native-aes-crypto';
import {
DEFAULT_ITERATIONS,
DEFAULT_KEY_LENGTH,
DEFAULT_SALT,
} from '../constants';
import type { EncryptionOptions } from '../interfaces';

Expand Down Expand Up @@ -82,13 +81,13 @@ const createIV = (length = IV_LENGTH): string => {

const generateEncryptionKey = async (
encryptKey: string,
options?: EncryptionOptions
options: EncryptionOptions
): Promise<string> => {
const {
salt = DEFAULT_SALT,
salt,
iterations = DEFAULT_ITERATIONS,
keyLength = DEFAULT_KEY_LENGTH,
} = options || {};
} = options;

try {
return await generateKey(encryptKey, salt, iterations, keyLength);
Expand Down
Loading