Chore: Migrate lib/encryption folder to TypeScript (#3639)
* Initial commit * add types/bytebuffer, add type definitions to params and update interfaces * add more types and type assertions * update types * change bang operator by type assertion and update class variables definitions * add types for deferred class * minor tweaks on types definitions * add ts-ignore * Update encryption.ts * update deferred and encryption * update encryption.ts * Update room.ts * update toDecrypt type * initialize sessionKeyExportedString * remove return types
This commit is contained in:
parent
97f8271127
commit
352a718631
|
@ -3,7 +3,8 @@ import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
import { IAttachment } from './IAttachment';
|
import { IAttachment } from './IAttachment';
|
||||||
import { IReaction } from './IReaction';
|
import { IReaction } from './IReaction';
|
||||||
import { SubscriptionType } from './ISubscription';
|
|
||||||
|
export type MessageType = 'jitsi_call_started' | 'discussion-created' | 'e2e' | 'load_more' | 'rm' | 'uj';
|
||||||
|
|
||||||
export interface IUserMessage {
|
export interface IUserMessage {
|
||||||
_id: string;
|
_id: string;
|
||||||
|
@ -34,12 +35,16 @@ export interface ITranslations {
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type E2EType = 'pending' | 'done';
|
||||||
|
|
||||||
export interface ILastMessage {
|
export interface ILastMessage {
|
||||||
_id: string;
|
_id: string;
|
||||||
rid: string;
|
rid: string;
|
||||||
tshow: boolean;
|
tshow: boolean;
|
||||||
|
t: MessageType;
|
||||||
tmid: string;
|
tmid: string;
|
||||||
msg: string;
|
msg: string;
|
||||||
|
e2e: E2EType;
|
||||||
ts: Date;
|
ts: Date;
|
||||||
u: IUserMessage;
|
u: IUserMessage;
|
||||||
_updatedAt: Date;
|
_updatedAt: Date;
|
||||||
|
@ -55,8 +60,9 @@ export interface ILastMessage {
|
||||||
|
|
||||||
export interface IMessage {
|
export interface IMessage {
|
||||||
_id: string;
|
_id: string;
|
||||||
|
rid: string;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
t?: SubscriptionType;
|
t?: MessageType;
|
||||||
ts: Date;
|
ts: Date;
|
||||||
u: IUserMessage;
|
u: IUserMessage;
|
||||||
alias: string;
|
alias: string;
|
||||||
|
|
|
@ -12,6 +12,7 @@ export enum SubscriptionType {
|
||||||
DIRECT = 'd',
|
DIRECT = 'd',
|
||||||
CHANNEL = 'c',
|
CHANNEL = 'c',
|
||||||
OMNICHANNEL = 'l',
|
OMNICHANNEL = 'l',
|
||||||
|
E2E = 'e2e',
|
||||||
THREAD = 'thread' // FIXME: this is not a type of subscription
|
THREAD = 'thread' // FIXME: this is not a type of subscription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,8 @@ import Model from '@nozbe/watermelondb/Model';
|
||||||
import { MarkdownAST } from '@rocket.chat/message-parser';
|
import { MarkdownAST } from '@rocket.chat/message-parser';
|
||||||
|
|
||||||
import { IAttachment } from './IAttachment';
|
import { IAttachment } from './IAttachment';
|
||||||
import { IEditedBy, IUserChannel, IUserMention, IUserMessage } from './IMessage';
|
import { IEditedBy, IUserChannel, IUserMention, IUserMessage, MessageType } from './IMessage';
|
||||||
import { IReaction } from './IReaction';
|
import { IReaction } from './IReaction';
|
||||||
import { SubscriptionType } from './ISubscription';
|
|
||||||
import { IUrl } from './IUrl';
|
import { IUrl } from './IUrl';
|
||||||
|
|
||||||
interface IFileThread {
|
interface IFileThread {
|
||||||
|
@ -35,8 +34,9 @@ export interface IThreadResult {
|
||||||
|
|
||||||
export interface IThread {
|
export interface IThread {
|
||||||
id: string;
|
id: string;
|
||||||
|
tmsg?: string;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
t?: SubscriptionType;
|
t?: MessageType;
|
||||||
rid: string;
|
rid: string;
|
||||||
_updatedAt?: Date;
|
_updatedAt?: Date;
|
||||||
ts?: Date;
|
ts?: Date;
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import Model from '@nozbe/watermelondb/Model';
|
import Model from '@nozbe/watermelondb/Model';
|
||||||
|
|
||||||
import { IAttachment } from './IAttachment';
|
import { IAttachment } from './IAttachment';
|
||||||
import { IEditedBy, ITranslations, IUserChannel, IUserMention, IUserMessage } from './IMessage';
|
import { IEditedBy, ITranslations, IUserChannel, IUserMention, IUserMessage, MessageType } from './IMessage';
|
||||||
import { IReaction } from './IReaction';
|
import { IReaction } from './IReaction';
|
||||||
import { SubscriptionType } from './ISubscription';
|
|
||||||
|
|
||||||
export interface IThreadMessage {
|
export interface IThreadMessage {
|
||||||
|
tmsg?: string;
|
||||||
msg?: string;
|
msg?: string;
|
||||||
t?: SubscriptionType;
|
t?: MessageType;
|
||||||
rid: string;
|
rid: string;
|
||||||
ts: Date;
|
ts: Date;
|
||||||
u: IUserMessage;
|
u: IUserMessage;
|
||||||
|
|
|
@ -99,8 +99,8 @@ export interface IUser extends IRocketChatRecord {
|
||||||
roles: string[];
|
roles: string[];
|
||||||
type: string;
|
type: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
username?: string;
|
|
||||||
name?: string;
|
name?: string;
|
||||||
|
username: string;
|
||||||
services?: IUserServices;
|
services?: IUserServices;
|
||||||
emails?: IUserEmail[];
|
emails?: IUserEmail[];
|
||||||
status?: UserStatus;
|
status?: UserStatus;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import EJSON from 'ejson';
|
import EJSON from 'ejson';
|
||||||
import SimpleCrypto from 'react-native-simple-crypto';
|
import SimpleCrypto from 'react-native-simple-crypto';
|
||||||
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
|
||||||
import { Q } from '@nozbe/watermelondb';
|
import { Q, Model } from '@nozbe/watermelondb';
|
||||||
|
|
||||||
import RocketChat from '../rocketchat';
|
import RocketChat from '../rocketchat';
|
||||||
import UserPreferences from '../userPreferences';
|
import UserPreferences from '../userPreferences';
|
||||||
|
@ -20,9 +20,25 @@ import {
|
||||||
} from './constants';
|
} from './constants';
|
||||||
import { joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils';
|
import { joinVectorData, randomPassword, splitVectorData, toString, utf8ToBuffer } from './utils';
|
||||||
import { EncryptionRoom } from './index';
|
import { EncryptionRoom } from './index';
|
||||||
|
import { IMessage, ISubscription, TMessageModel, TSubscriptionModel, TThreadMessageModel, TThreadModel } from '../../definitions';
|
||||||
|
|
||||||
class Encryption {
|
class Encryption {
|
||||||
|
ready: boolean;
|
||||||
|
privateKey: string | null;
|
||||||
|
readyPromise: Deferred;
|
||||||
|
userId: string | null;
|
||||||
|
roomInstances: {
|
||||||
|
[rid: string]: {
|
||||||
|
ready: boolean;
|
||||||
|
provideKeyToUser: Function;
|
||||||
|
handshake: Function;
|
||||||
|
decrypt: Function;
|
||||||
|
encrypt: Function;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.userId = '';
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
this.privateKey = null;
|
this.privateKey = null;
|
||||||
this.roomInstances = {};
|
this.roomInstances = {};
|
||||||
|
@ -37,7 +53,7 @@ class Encryption {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Encryption client
|
// Initialize Encryption client
|
||||||
initialize = userId => {
|
initialize = (userId: string) => {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.roomInstances = {};
|
this.roomInstances = {};
|
||||||
|
|
||||||
|
@ -82,7 +98,7 @@ class Encryption {
|
||||||
};
|
};
|
||||||
|
|
||||||
// When a new participant join and request a new room encryption key
|
// When a new participant join and request a new room encryption key
|
||||||
provideRoomKeyToUser = async (keyId, rid) => {
|
provideRoomKeyToUser = async (keyId: string, rid: string) => {
|
||||||
// If the client is not ready
|
// If the client is not ready
|
||||||
if (!this.ready) {
|
if (!this.ready) {
|
||||||
try {
|
try {
|
||||||
|
@ -100,14 +116,14 @@ class Encryption {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Persist keys on UserPreferences
|
// Persist keys on UserPreferences
|
||||||
persistKeys = async (server, publicKey, privateKey) => {
|
persistKeys = async (server: string, publicKey: string, privateKey: string) => {
|
||||||
this.privateKey = await SimpleCrypto.RSA.importKey(EJSON.parse(privateKey));
|
this.privateKey = await SimpleCrypto.RSA.importKey(EJSON.parse(privateKey));
|
||||||
await UserPreferences.setStringAsync(`${server}-${E2E_PUBLIC_KEY}`, EJSON.stringify(publicKey));
|
await UserPreferences.setStringAsync(`${server}-${E2E_PUBLIC_KEY}`, EJSON.stringify(publicKey));
|
||||||
await UserPreferences.setStringAsync(`${server}-${E2E_PRIVATE_KEY}`, privateKey);
|
await UserPreferences.setStringAsync(`${server}-${E2E_PRIVATE_KEY}`, privateKey);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Could not obtain public-private keypair from server.
|
// Could not obtain public-private keypair from server.
|
||||||
createKeys = async (userId, server) => {
|
createKeys = async (userId: string, server: string) => {
|
||||||
// Generate new keys
|
// Generate new keys
|
||||||
const key = await SimpleCrypto.RSA.generateKeys(2048);
|
const key = await SimpleCrypto.RSA.generateKeys(2048);
|
||||||
|
|
||||||
|
@ -132,7 +148,7 @@ class Encryption {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Encode a private key before send it to the server
|
// Encode a private key before send it to the server
|
||||||
encodePrivateKey = async (privateKey, password, userId) => {
|
encodePrivateKey = async (privateKey: string, password: string, userId: string) => {
|
||||||
const masterKey = await this.generateMasterKey(password, userId);
|
const masterKey = await this.generateMasterKey(password, userId);
|
||||||
|
|
||||||
const vector = await SimpleCrypto.utils.randomBytes(16);
|
const vector = await SimpleCrypto.utils.randomBytes(16);
|
||||||
|
@ -142,7 +158,7 @@ class Encryption {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decode a private key fetched from server
|
// Decode a private key fetched from server
|
||||||
decodePrivateKey = async (privateKey, password, userId) => {
|
decodePrivateKey = async (privateKey: string, password: string, userId: string) => {
|
||||||
const masterKey = await this.generateMasterKey(password, userId);
|
const masterKey = await this.generateMasterKey(password, userId);
|
||||||
const [vector, cipherText] = splitVectorData(EJSON.parse(privateKey));
|
const [vector, cipherText] = splitVectorData(EJSON.parse(privateKey));
|
||||||
|
|
||||||
|
@ -152,7 +168,7 @@ class Encryption {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate a user master key, this is based on userId and a password
|
// Generate a user master key, this is based on userId and a password
|
||||||
generateMasterKey = async (password, userId) => {
|
generateMasterKey = async (password: string, userId: string) => {
|
||||||
const iterations = 1000;
|
const iterations = 1000;
|
||||||
const hash = 'SHA256';
|
const hash = 'SHA256';
|
||||||
const keyLen = 32;
|
const keyLen = 32;
|
||||||
|
@ -166,18 +182,18 @@ class Encryption {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a random password to local created keys
|
// Create a random password to local created keys
|
||||||
createRandomPassword = async server => {
|
createRandomPassword = async (server: string) => {
|
||||||
const password = randomPassword();
|
const password = randomPassword();
|
||||||
await UserPreferences.setStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`, password);
|
await UserPreferences.setStringAsync(`${server}-${E2E_RANDOM_PASSWORD_KEY}`, password);
|
||||||
return password;
|
return password;
|
||||||
};
|
};
|
||||||
|
|
||||||
changePassword = async (server, password) => {
|
changePassword = async (server: string, password: string) => {
|
||||||
// Cast key to the format server is expecting
|
// Cast key to the format server is expecting
|
||||||
const privateKey = await SimpleCrypto.RSA.exportKey(this.privateKey);
|
const privateKey = await SimpleCrypto.RSA.exportKey(this.privateKey as string);
|
||||||
|
|
||||||
// Encode the private key
|
// Encode the private key
|
||||||
const encodedPrivateKey = await this.encodePrivateKey(EJSON.stringify(privateKey), password, this.userId);
|
const encodedPrivateKey = await this.encodePrivateKey(EJSON.stringify(privateKey), password, this.userId as string);
|
||||||
const publicKey = await UserPreferences.getStringAsync(`${server}-${E2E_PUBLIC_KEY}`);
|
const publicKey = await UserPreferences.getStringAsync(`${server}-${E2E_PUBLIC_KEY}`);
|
||||||
|
|
||||||
// Send the new keys to the server
|
// Send the new keys to the server
|
||||||
|
@ -185,7 +201,7 @@ class Encryption {
|
||||||
};
|
};
|
||||||
|
|
||||||
// get a encryption room instance
|
// get a encryption room instance
|
||||||
getRoomInstance = async rid => {
|
getRoomInstance = async (rid: string) => {
|
||||||
// Prevent handshake again
|
// Prevent handshake again
|
||||||
if (this.roomInstances[rid]?.ready) {
|
if (this.roomInstances[rid]?.ready) {
|
||||||
return this.roomInstances[rid];
|
return this.roomInstances[rid];
|
||||||
|
@ -193,7 +209,7 @@ class Encryption {
|
||||||
|
|
||||||
// If doesn't have a instance of this room
|
// If doesn't have a instance of this room
|
||||||
if (!this.roomInstances[rid]) {
|
if (!this.roomInstances[rid]) {
|
||||||
this.roomInstances[rid] = new EncryptionRoom(rid, this.userId);
|
this.roomInstances[rid] = new EncryptionRoom(rid, this.userId as string);
|
||||||
}
|
}
|
||||||
|
|
||||||
const roomE2E = this.roomInstances[rid];
|
const roomE2E = this.roomInstances[rid];
|
||||||
|
@ -206,7 +222,7 @@ class Encryption {
|
||||||
|
|
||||||
// Logic to decrypt all pending messages/threads/threadMessages
|
// Logic to decrypt all pending messages/threads/threadMessages
|
||||||
// after initialize the encryption client
|
// after initialize the encryption client
|
||||||
decryptPendingMessages = async roomId => {
|
decryptPendingMessages = async (roomId?: string) => {
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
|
|
||||||
const messagesCollection = db.get('messages');
|
const messagesCollection = db.get('messages');
|
||||||
|
@ -228,8 +244,12 @@ class Encryption {
|
||||||
const threadMessagesToDecrypt = await threadMessagesCollection.query(...whereClause).fetch();
|
const threadMessagesToDecrypt = await threadMessagesCollection.query(...whereClause).fetch();
|
||||||
|
|
||||||
// Concat messages/threads/threadMessages
|
// Concat messages/threads/threadMessages
|
||||||
let toDecrypt = [...messagesToDecrypt, ...threadsToDecrypt, ...threadMessagesToDecrypt];
|
let toDecrypt: (TThreadModel | TThreadMessageModel)[] = [
|
||||||
toDecrypt = await Promise.all(
|
...messagesToDecrypt,
|
||||||
|
...threadsToDecrypt,
|
||||||
|
...threadMessagesToDecrypt
|
||||||
|
];
|
||||||
|
toDecrypt = (await Promise.all(
|
||||||
toDecrypt.map(async message => {
|
toDecrypt.map(async message => {
|
||||||
const { t, msg, tmsg } = message;
|
const { t, msg, tmsg } = message;
|
||||||
const { id: rid } = message.subscription;
|
const { id: rid } = message.subscription;
|
||||||
|
@ -240,9 +260,10 @@ class Encryption {
|
||||||
msg,
|
msg,
|
||||||
tmsg
|
tmsg
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return message.prepareUpdate(
|
return message.prepareUpdate(
|
||||||
protectedFunction(m => {
|
protectedFunction((m: TMessageModel) => {
|
||||||
Object.assign(m, newMessage);
|
Object.assign(m, newMessage);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -250,9 +271,9 @@ class Encryption {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
)) as (TThreadModel | TThreadMessageModel)[];
|
||||||
|
|
||||||
await db.action(async () => {
|
await db.write(async () => {
|
||||||
await db.batch(...toDecrypt);
|
await db.batch(...toDecrypt);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -271,19 +292,19 @@ class Encryption {
|
||||||
const subsEncrypted = await subCollection.query(Q.where('e2e_key_id', Q.notEq(null))).fetch();
|
const subsEncrypted = await subCollection.query(Q.where('e2e_key_id', Q.notEq(null))).fetch();
|
||||||
// We can't do this on database level since lastMessage is not a database object
|
// We can't do this on database level since lastMessage is not a database object
|
||||||
const subsToDecrypt = subsEncrypted.filter(
|
const subsToDecrypt = subsEncrypted.filter(
|
||||||
sub =>
|
(sub: ISubscription) =>
|
||||||
// Encrypted message
|
// Encrypted message
|
||||||
sub?.lastMessage?.t === E2E_MESSAGE_TYPE &&
|
sub?.lastMessage?.t === E2E_MESSAGE_TYPE &&
|
||||||
// Message pending decrypt
|
// Message pending decrypt
|
||||||
sub?.lastMessage?.e2e === E2E_STATUS.PENDING
|
sub?.lastMessage?.e2e === E2E_STATUS.PENDING
|
||||||
);
|
);
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
subsToDecrypt.map(async sub => {
|
subsToDecrypt.map(async (sub: TSubscriptionModel) => {
|
||||||
const { rid, lastMessage } = sub;
|
const { rid, lastMessage } = sub;
|
||||||
const newSub = await this.decryptSubscription({ rid, lastMessage });
|
const newSub = await this.decryptSubscription({ rid, lastMessage });
|
||||||
try {
|
try {
|
||||||
return sub.prepareUpdate(
|
return sub.prepareUpdate(
|
||||||
protectedFunction(m => {
|
protectedFunction((m: TSubscriptionModel) => {
|
||||||
Object.assign(m, newSub);
|
Object.assign(m, newSub);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -293,7 +314,7 @@ class Encryption {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
await db.action(async () => {
|
await db.write(async () => {
|
||||||
await db.batch(...subsToDecrypt);
|
await db.batch(...subsToDecrypt);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -302,7 +323,7 @@ class Encryption {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decrypt a subscription lastMessage
|
// Decrypt a subscription lastMessage
|
||||||
decryptSubscription = async subscription => {
|
decryptSubscription = async (subscription: Partial<ISubscription>) => {
|
||||||
// If the subscription doesn't have a lastMessage just return
|
// If the subscription doesn't have a lastMessage just return
|
||||||
if (!subscription?.lastMessage) {
|
if (!subscription?.lastMessage) {
|
||||||
return subscription;
|
return subscription;
|
||||||
|
@ -334,18 +355,18 @@ class Encryption {
|
||||||
|
|
||||||
let subRecord;
|
let subRecord;
|
||||||
try {
|
try {
|
||||||
subRecord = await subCollection.find(rid);
|
subRecord = await subCollection.find(rid as string);
|
||||||
} catch {
|
} catch {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const batch = [];
|
const batch: (Model | null | void | false | Promise<void>)[] = [];
|
||||||
// If the subscription doesn't exists yet
|
// If the subscription doesn't exists yet
|
||||||
if (!subRecord) {
|
if (!subRecord) {
|
||||||
// Let's create the subscription with the data received
|
// Let's create the subscription with the data received
|
||||||
batch.push(
|
batch.push(
|
||||||
subCollection.prepareCreate(s => {
|
subCollection.prepareCreate((s: TSubscriptionModel) => {
|
||||||
s._raw = sanitizedRaw({ id: rid }, subCollection.schema);
|
s._raw = sanitizedRaw({ id: rid }, subCollection.schema);
|
||||||
Object.assign(s, subscription);
|
Object.assign(s, subscription);
|
||||||
})
|
})
|
||||||
|
@ -355,7 +376,7 @@ class Encryption {
|
||||||
try {
|
try {
|
||||||
// Let's update the subscription with the received E2EKey
|
// Let's update the subscription with the received E2EKey
|
||||||
batch.push(
|
batch.push(
|
||||||
subRecord.prepareUpdate(s => {
|
subRecord.prepareUpdate((s: TSubscriptionModel) => {
|
||||||
s.E2EKey = subscription.E2EKey;
|
s.E2EKey = subscription.E2EKey;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -366,7 +387,7 @@ class Encryption {
|
||||||
|
|
||||||
// If batch has some operation
|
// If batch has some operation
|
||||||
if (batch.length) {
|
if (batch.length) {
|
||||||
await db.action(async () => {
|
await db.write(async () => {
|
||||||
await db.batch(...batch);
|
await db.batch(...batch);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -377,7 +398,7 @@ class Encryption {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a instance using the subscription
|
// Get a instance using the subscription
|
||||||
const roomE2E = await this.getRoomInstance(rid);
|
const roomE2E = await this.getRoomInstance(rid as string);
|
||||||
const decryptedMessage = await roomE2E.decrypt(lastMessage);
|
const decryptedMessage = await roomE2E.decrypt(lastMessage);
|
||||||
return {
|
return {
|
||||||
...subscription,
|
...subscription,
|
||||||
|
@ -386,7 +407,7 @@ class Encryption {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Encrypt a message
|
// Encrypt a message
|
||||||
encryptMessage = async message => {
|
encryptMessage = async (message: IMessage) => {
|
||||||
const { rid } = message;
|
const { rid } = message;
|
||||||
const db = database.active;
|
const db = database.active;
|
||||||
const subCollection = db.get('subscriptions');
|
const subCollection = db.get('subscriptions');
|
||||||
|
@ -419,7 +440,7 @@ class Encryption {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decrypt a message
|
// Decrypt a message
|
||||||
decryptMessage = async message => {
|
decryptMessage = async (message: Partial<IMessage>) => {
|
||||||
const { t, e2e } = message;
|
const { t, e2e } = message;
|
||||||
|
|
||||||
// Prevent create a new instance if this room was encrypted sometime ago
|
// Prevent create a new instance if this room was encrypted sometime ago
|
||||||
|
@ -440,15 +461,15 @@ class Encryption {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { rid } = message;
|
const { rid } = message;
|
||||||
const roomE2E = await this.getRoomInstance(rid);
|
const roomE2E = await this.getRoomInstance(rid as string);
|
||||||
return roomE2E.decrypt(message);
|
return roomE2E.decrypt(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decrypt multiple messages
|
// Decrypt multiple messages
|
||||||
decryptMessages = messages => Promise.all(messages.map(m => this.decryptMessage(m)));
|
decryptMessages = (messages: IMessage[]) => Promise.all(messages.map((m: IMessage) => this.decryptMessage(m)));
|
||||||
|
|
||||||
// Decrypt multiple subscriptions
|
// Decrypt multiple subscriptions
|
||||||
decryptSubscriptions = subscriptions => Promise.all(subscriptions.map(s => this.decryptSubscription(s)));
|
decryptSubscriptions = (subscriptions: ISubscription[]) => Promise.all(subscriptions.map(s => this.decryptSubscription(s)));
|
||||||
}
|
}
|
||||||
|
|
||||||
const encryption = new Encryption();
|
const encryption = new Encryption();
|
|
@ -1,7 +1,9 @@
|
||||||
import EJSON from 'ejson';
|
import EJSON from 'ejson';
|
||||||
import { Base64 } from 'js-base64';
|
import { Base64 } from 'js-base64';
|
||||||
import SimpleCrypto from 'react-native-simple-crypto';
|
import SimpleCrypto from 'react-native-simple-crypto';
|
||||||
|
import ByteBuffer from 'bytebuffer';
|
||||||
|
|
||||||
|
import { IMessage } from '../../definitions';
|
||||||
import RocketChat from '../rocketchat';
|
import RocketChat from '../rocketchat';
|
||||||
import Deferred from '../../utils/deferred';
|
import Deferred from '../../utils/deferred';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
|
@ -19,13 +21,26 @@ import {
|
||||||
utf8ToBuffer
|
utf8ToBuffer
|
||||||
} from './utils';
|
} from './utils';
|
||||||
import { Encryption } from './index';
|
import { Encryption } from './index';
|
||||||
|
import { IUser } from '../../definitions/IUser';
|
||||||
|
|
||||||
export default class EncryptionRoom {
|
export default class EncryptionRoom {
|
||||||
constructor(roomId, userId) {
|
ready: boolean;
|
||||||
|
roomId: string;
|
||||||
|
userId: string;
|
||||||
|
establishing: boolean;
|
||||||
|
readyPromise: Deferred;
|
||||||
|
sessionKeyExportedString: string | ByteBuffer;
|
||||||
|
keyID: string;
|
||||||
|
roomKey: ArrayBuffer;
|
||||||
|
|
||||||
|
constructor(roomId: string, userId: string) {
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
this.establishing = false;
|
this.establishing = false;
|
||||||
|
this.keyID = '';
|
||||||
|
this.sessionKeyExportedString = '';
|
||||||
|
this.roomKey = new ArrayBuffer(0);
|
||||||
this.readyPromise = new Deferred();
|
this.readyPromise = new Deferred();
|
||||||
this.readyPromise.then(() => {
|
this.readyPromise.then(() => {
|
||||||
// Mark as ready
|
// Mark as ready
|
||||||
|
@ -57,7 +72,7 @@ export default class EncryptionRoom {
|
||||||
const { E2EKey, e2eKeyId } = subscription;
|
const { E2EKey, e2eKeyId } = subscription;
|
||||||
|
|
||||||
// If this room has a E2EKey, we import it
|
// If this room has a E2EKey, we import it
|
||||||
if (E2EKey) {
|
if (E2EKey && Encryption.privateKey) {
|
||||||
// We're establishing a new room encryption client
|
// We're establishing a new room encryption client
|
||||||
this.establishing = true;
|
this.establishing = true;
|
||||||
await this.importRoomKey(E2EKey, Encryption.privateKey);
|
await this.importRoomKey(E2EKey, Encryption.privateKey);
|
||||||
|
@ -82,25 +97,25 @@ export default class EncryptionRoom {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Import roomKey as an AES Decrypt key
|
// Import roomKey as an AES Decrypt key
|
||||||
importRoomKey = async (E2EKey, privateKey) => {
|
importRoomKey = async (E2EKey: string, privateKey: string) => {
|
||||||
const roomE2EKey = E2EKey.slice(12);
|
const roomE2EKey = E2EKey.slice(12);
|
||||||
|
|
||||||
const decryptedKey = await SimpleCrypto.RSA.decrypt(roomE2EKey, privateKey);
|
const decryptedKey = await SimpleCrypto.RSA.decrypt(roomE2EKey, privateKey);
|
||||||
this.sessionKeyExportedString = toString(decryptedKey);
|
this.sessionKeyExportedString = toString(decryptedKey);
|
||||||
|
|
||||||
this.keyID = Base64.encode(this.sessionKeyExportedString).slice(0, 12);
|
this.keyID = Base64.encode(this.sessionKeyExportedString as string).slice(0, 12);
|
||||||
|
|
||||||
// Extract K from Web Crypto Secret Key
|
// Extract K from Web Crypto Secret Key
|
||||||
// K is a base64URL encoded array of bytes
|
// K is a base64URL encoded array of bytes
|
||||||
// Web Crypto API uses this as a private key to decrypt/encrypt things
|
// Web Crypto API uses this as a private key to decrypt/encrypt things
|
||||||
// Reference: https://www.javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/5.1/com/nimbusds/jose/jwk/OctetSequenceKey.html
|
// Reference: https://www.javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/5.1/com/nimbusds/jose/jwk/OctetSequenceKey.html
|
||||||
const { k } = EJSON.parse(this.sessionKeyExportedString);
|
const { k } = EJSON.parse(this.sessionKeyExportedString as string);
|
||||||
this.roomKey = b64ToBuffer(k);
|
this.roomKey = b64ToBuffer(k);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a key to a room
|
// Create a key to a room
|
||||||
createRoomKey = async () => {
|
createRoomKey = async () => {
|
||||||
const key = await SimpleCrypto.utils.randomBytes(16);
|
const key = (await SimpleCrypto.utils.randomBytes(16)) as Uint8Array;
|
||||||
this.roomKey = key;
|
this.roomKey = key;
|
||||||
|
|
||||||
// Web Crypto format of a Secret Key
|
// Web Crypto format of a Secret Key
|
||||||
|
@ -131,7 +146,7 @@ export default class EncryptionRoom {
|
||||||
// Each time you see a encrypted message of a room that you don't have a key
|
// Each time you see a encrypted message of a room that you don't have a key
|
||||||
// this will be called again and run once in 5 seconds
|
// this will be called again and run once in 5 seconds
|
||||||
requestRoomKey = debounce(
|
requestRoomKey = debounce(
|
||||||
async e2eKeyId => {
|
async (e2eKeyId: string) => {
|
||||||
await RocketChat.e2eRequestRoomKey(this.roomId, e2eKeyId);
|
await RocketChat.e2eRequestRoomKey(this.roomId, e2eKeyId);
|
||||||
},
|
},
|
||||||
5000,
|
5000,
|
||||||
|
@ -143,22 +158,22 @@ export default class EncryptionRoom {
|
||||||
const result = await RocketChat.e2eGetUsersOfRoomWithoutKey(this.roomId);
|
const result = await RocketChat.e2eGetUsersOfRoomWithoutKey(this.roomId);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
const { users } = result;
|
const { users } = result;
|
||||||
await Promise.all(users.map(user => this.encryptRoomKeyForUser(user)));
|
await Promise.all(users.map((user: IUser) => this.encryptRoomKeyForUser(user)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Encrypt the room key to each user in
|
// Encrypt the room key to each user in
|
||||||
encryptRoomKeyForUser = async user => {
|
encryptRoomKeyForUser = async (user: IUser) => {
|
||||||
if (user?.e2e?.public_key) {
|
if (user?.e2e?.public_key) {
|
||||||
const { public_key: publicKey } = user.e2e;
|
const { public_key: publicKey } = user.e2e;
|
||||||
const userKey = await SimpleCrypto.RSA.importKey(EJSON.parse(publicKey));
|
const userKey = await SimpleCrypto.RSA.importKey(EJSON.parse(publicKey));
|
||||||
const encryptedUserKey = await SimpleCrypto.RSA.encrypt(this.sessionKeyExportedString, userKey);
|
const encryptedUserKey = await SimpleCrypto.RSA.encrypt(this.sessionKeyExportedString as string, userKey);
|
||||||
await RocketChat.e2eUpdateGroupKey(user?._id, this.roomId, this.keyID + encryptedUserKey);
|
await RocketChat.e2eUpdateGroupKey(user?._id, this.roomId, this.keyID + encryptedUserKey);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Provide this room key to a user
|
// Provide this room key to a user
|
||||||
provideKeyToUser = async keyId => {
|
provideKeyToUser = async (keyId: string) => {
|
||||||
// Don't provide a key if the keyId received
|
// Don't provide a key if the keyId received
|
||||||
// is different than the current one
|
// is different than the current one
|
||||||
if (this.keyID !== keyId) {
|
if (this.keyID !== keyId) {
|
||||||
|
@ -169,16 +184,16 @@ export default class EncryptionRoom {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Encrypt text
|
// Encrypt text
|
||||||
encryptText = async text => {
|
encryptText = async (text: string | ArrayBuffer) => {
|
||||||
text = utf8ToBuffer(text);
|
text = utf8ToBuffer(text as string);
|
||||||
const vector = await SimpleCrypto.utils.randomBytes(16);
|
const vector = await SimpleCrypto.utils.randomBytes(16);
|
||||||
const data = await SimpleCrypto.AES.encrypt(text, this.roomKey, vector);
|
const data = await SimpleCrypto.AES.encrypt(text, this.roomKey as ArrayBuffer, vector);
|
||||||
|
|
||||||
return this.keyID + bufferToB64(joinVectorData(vector, data));
|
return this.keyID + bufferToB64(joinVectorData(vector, data));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Encrypt messages
|
// Encrypt messages
|
||||||
encrypt = async message => {
|
encrypt = async (message: IMessage) => {
|
||||||
if (!this.ready) {
|
if (!this.ready) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
@ -207,8 +222,8 @@ export default class EncryptionRoom {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decrypt text
|
// Decrypt text
|
||||||
decryptText = async msg => {
|
decryptText = async (msg: string | ArrayBuffer) => {
|
||||||
msg = b64ToBuffer(msg.slice(12));
|
msg = b64ToBuffer(msg.slice(12) as string);
|
||||||
const [vector, cipherText] = splitVectorData(msg);
|
const [vector, cipherText] = splitVectorData(msg);
|
||||||
|
|
||||||
const decrypted = await SimpleCrypto.AES.decrypt(cipherText, this.roomKey, vector);
|
const decrypted = await SimpleCrypto.AES.decrypt(cipherText, this.roomKey, vector);
|
||||||
|
@ -219,7 +234,7 @@ export default class EncryptionRoom {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Decrypt messages
|
// Decrypt messages
|
||||||
decrypt = async message => {
|
decrypt = async (message: IMessage) => {
|
||||||
if (!this.ready) {
|
if (!this.ready) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
@ -231,7 +246,7 @@ export default class EncryptionRoom {
|
||||||
if (t === E2E_MESSAGE_TYPE && e2e !== E2E_STATUS.DONE) {
|
if (t === E2E_MESSAGE_TYPE && e2e !== E2E_STATUS.DONE) {
|
||||||
let { msg, tmsg } = message;
|
let { msg, tmsg } = message;
|
||||||
// Decrypt msg
|
// Decrypt msg
|
||||||
msg = await this.decryptText(msg);
|
msg = await this.decryptText(msg as string);
|
||||||
|
|
||||||
// Decrypt tmsg
|
// Decrypt tmsg
|
||||||
if (tmsg) {
|
if (tmsg) {
|
|
@ -1,4 +1,3 @@
|
||||||
/* eslint-disable no-bitwise */
|
|
||||||
import ByteBuffer from 'bytebuffer';
|
import ByteBuffer from 'bytebuffer';
|
||||||
import SimpleCrypto from 'react-native-simple-crypto';
|
import SimpleCrypto from 'react-native-simple-crypto';
|
||||||
|
|
||||||
|
@ -7,12 +6,13 @@ import { fromByteArray, toByteArray } from '../../utils/base64-js';
|
||||||
|
|
||||||
const BASE64URI = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
const BASE64URI = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
||||||
|
|
||||||
export const b64ToBuffer = base64 => toByteArray(base64).buffer;
|
// @ts-ignore
|
||||||
|
export const b64ToBuffer = (base64: string): ArrayBuffer => toByteArray(base64).buffer;
|
||||||
export const utf8ToBuffer = SimpleCrypto.utils.convertUtf8ToArrayBuffer;
|
export const utf8ToBuffer = SimpleCrypto.utils.convertUtf8ToArrayBuffer;
|
||||||
export const bufferToB64 = arrayBuffer => fromByteArray(new Uint8Array(arrayBuffer));
|
export const bufferToB64 = (arrayBuffer: ArrayBuffer): string => fromByteArray(new Uint8Array(arrayBuffer));
|
||||||
// ArrayBuffer -> Base64 URI Safe
|
// ArrayBuffer -> Base64 URI Safe
|
||||||
// https://github.com/herrjemand/Base64URL-ArrayBuffer/blob/master/lib/base64url-arraybuffer.js
|
// https://github.com/herrjemand/Base64URL-ArrayBuffer/blob/master/lib/base64url-arraybuffer.js
|
||||||
export const bufferToB64URI = buffer => {
|
export const bufferToB64URI = (buffer: ArrayBuffer): string => {
|
||||||
const uintArray = new Uint8Array(buffer);
|
const uintArray = new Uint8Array(buffer);
|
||||||
const len = uintArray.length;
|
const len = uintArray.length;
|
||||||
let base64 = '';
|
let base64 = '';
|
||||||
|
@ -33,28 +33,28 @@ export const bufferToB64URI = buffer => {
|
||||||
return base64;
|
return base64;
|
||||||
};
|
};
|
||||||
// SimpleCrypto.utils.convertArrayBufferToUtf8 is not working with unicode emoji
|
// SimpleCrypto.utils.convertArrayBufferToUtf8 is not working with unicode emoji
|
||||||
export const bufferToUtf8 = buffer => {
|
export const bufferToUtf8 = (buffer: ArrayBuffer): string => {
|
||||||
const uintArray = new Uint8Array(buffer);
|
const uintArray = new Uint8Array(buffer) as number[] & Uint8Array;
|
||||||
const encodedString = String.fromCharCode.apply(null, uintArray);
|
const encodedString = String.fromCharCode.apply(null, uintArray);
|
||||||
const decodedString = decodeURIComponent(escape(encodedString));
|
return decodeURIComponent(escape(encodedString));
|
||||||
return decodedString;
|
|
||||||
};
|
};
|
||||||
export const splitVectorData = text => {
|
export const splitVectorData = (text: ArrayBuffer): ArrayBuffer[] => {
|
||||||
const vector = text.slice(0, 16);
|
const vector = text.slice(0, 16);
|
||||||
const data = text.slice(16);
|
const data = text.slice(16);
|
||||||
return [vector, data];
|
return [vector, data];
|
||||||
};
|
};
|
||||||
export const joinVectorData = (vector, data) => {
|
|
||||||
|
export const joinVectorData = (vector: ArrayBuffer, data: ArrayBuffer): ArrayBufferLike => {
|
||||||
const output = new Uint8Array(vector.byteLength + data.byteLength);
|
const output = new Uint8Array(vector.byteLength + data.byteLength);
|
||||||
output.set(new Uint8Array(vector), 0);
|
output.set(new Uint8Array(vector), 0);
|
||||||
output.set(new Uint8Array(data), vector.byteLength);
|
output.set(new Uint8Array(data), vector.byteLength);
|
||||||
return output.buffer;
|
return output.buffer;
|
||||||
};
|
};
|
||||||
export const toString = thing => {
|
export const toString = (thing: string | ByteBuffer | Buffer | ArrayBuffer | Uint8Array): string | ByteBuffer => {
|
||||||
if (typeof thing === 'string') {
|
if (typeof thing === 'string') {
|
||||||
return thing;
|
return thing;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line new-cap
|
// @ts-ignore
|
||||||
return new ByteBuffer.wrap(thing).toString('binary');
|
return new ByteBuffer.wrap(thing).toString('binary');
|
||||||
};
|
};
|
||||||
export const randomPassword = () => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase();
|
export const randomPassword = (): string => `${random(3)}-${random(3)}-${random(3)}`.toLowerCase();
|
|
@ -37,7 +37,7 @@ const getLens = (b64: string) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
// base64 is 4/3 + up to two characters of the original data
|
// base64 is 4/3 + up to two characters of the original data
|
||||||
export const byteLength = (b64: string) => {
|
export const byteLength = (b64: string): number => {
|
||||||
const lens = getLens(b64);
|
const lens = getLens(b64);
|
||||||
const validLen = lens[0];
|
const validLen = lens[0];
|
||||||
const placeHoldersLen = lens[1];
|
const placeHoldersLen = lens[1];
|
||||||
|
@ -47,7 +47,7 @@ export const byteLength = (b64: string) => {
|
||||||
const _byteLength = (b64: string, validLen: number, placeHoldersLen: number) =>
|
const _byteLength = (b64: string, validLen: number, placeHoldersLen: number) =>
|
||||||
((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
|
((validLen + placeHoldersLen) * 3) / 4 - placeHoldersLen;
|
||||||
|
|
||||||
export const toByteArray = (b64: string) => {
|
export const toByteArray = (b64: string): any[] | Uint8Array => {
|
||||||
let tmp;
|
let tmp;
|
||||||
const lens = getLens(b64);
|
const lens = getLens(b64);
|
||||||
const validLen = lens[0];
|
const validLen = lens[0];
|
||||||
|
@ -106,7 +106,7 @@ const encodeChunk = (uint8: number[] | Uint8Array, start: number, end: number) =
|
||||||
return output.join('');
|
return output.join('');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fromByteArray = (uint8: number[] | Uint8Array) => {
|
export const fromByteArray = (uint8: number[] | Uint8Array): string => {
|
||||||
let tmp;
|
let tmp;
|
||||||
const len = uint8.length;
|
const len = uint8.length;
|
||||||
const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
|
const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
// https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred
|
|
||||||
export default class Deferred {
|
|
||||||
constructor() {
|
|
||||||
const promise = new Promise((resolve, reject) => {
|
|
||||||
this.resolve = resolve;
|
|
||||||
this.reject = reject;
|
|
||||||
});
|
|
||||||
|
|
||||||
promise.resolve = this.resolve;
|
|
||||||
promise.reject = this.reject;
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
export default class Deferred {
|
||||||
|
[Symbol.toStringTag]: 'Promise';
|
||||||
|
|
||||||
|
private promise: Promise<unknown>;
|
||||||
|
private _resolve: (value?: unknown) => void;
|
||||||
|
private _reject: (reason?: any) => void;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._resolve = () => {};
|
||||||
|
this._reject = () => {};
|
||||||
|
this.promise = new Promise((resolve, reject) => {
|
||||||
|
this._resolve = resolve as (value?: unknown) => void;
|
||||||
|
this._reject = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public then<TResult1, TResult2>(
|
||||||
|
onfulfilled?: ((value: unknown) => TResult1 | PromiseLike<TResult1>) | undefined | null,
|
||||||
|
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
|
||||||
|
): Promise<TResult1 | TResult2> {
|
||||||
|
return this.promise.then(onfulfilled, onrejected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public catch<TResult>(
|
||||||
|
onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null
|
||||||
|
): Promise<unknown | TResult> {
|
||||||
|
return this.promise.catch(onrejected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public finally(onfinally?: (() => void) | null | undefined): Promise<unknown> {
|
||||||
|
return this.promise.finally(onfinally);
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolve(value?: unknown): void {
|
||||||
|
this._resolve(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public reject(reason?: any): void {
|
||||||
|
this._reject(reason);
|
||||||
|
}
|
||||||
|
}
|
|
@ -143,6 +143,7 @@
|
||||||
"@rocket.chat/eslint-config": "^0.4.0",
|
"@rocket.chat/eslint-config": "^0.4.0",
|
||||||
"@storybook/addon-storyshots": "5.3.21",
|
"@storybook/addon-storyshots": "5.3.21",
|
||||||
"@storybook/react-native": "5.3.25",
|
"@storybook/react-native": "5.3.25",
|
||||||
|
"@types/bytebuffer": "^5.0.43",
|
||||||
"@testing-library/react-native": "^9.0.0",
|
"@testing-library/react-native": "^9.0.0",
|
||||||
"@types/ejson": "^2.1.3",
|
"@types/ejson": "^2.1.3",
|
||||||
"@types/jest": "^26.0.24",
|
"@types/jest": "^26.0.24",
|
||||||
|
|
13
yarn.lock
13
yarn.lock
|
@ -4200,6 +4200,14 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/types" "^7.3.0"
|
"@babel/types" "^7.3.0"
|
||||||
|
|
||||||
|
"@types/bytebuffer@^5.0.43":
|
||||||
|
version "5.0.43"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/bytebuffer/-/bytebuffer-5.0.43.tgz#b5259fca1412106bcee0cabfbf7c104846d06738"
|
||||||
|
integrity sha512-vQnTYvy4LpSojHjKdmg4nXFI1BAiYPvZ/k3ouczZAQnbDprk1xqxJiFmFHyy8y6MuUq3slz5erNMtn6n87uVKw==
|
||||||
|
dependencies:
|
||||||
|
"@types/long" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/color-name@^1.1.1":
|
"@types/color-name@^1.1.1":
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
|
||||||
|
@ -4341,6 +4349,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"
|
||||||
integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==
|
integrity sha512-/BHF5HAx3em7/KkzVKm3LrsD6HZAXuXO1AJZQ3cRRBZj4oHZDviWPYu0aEplAqDFNHZPW6d3G7KN+ONcCCC7pw==
|
||||||
|
|
||||||
|
"@types/long@*":
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
|
||||||
|
integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==
|
||||||
|
|
||||||
"@types/minimatch@*":
|
"@types/minimatch@*":
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
||||||
|
|
Loading…
Reference in New Issue