Merge pull request #1676 from Bilb/ons-resolve

ONS resolve on start conversation screen
pull/1682/head
Audric Ackermann 4 years ago committed by GitHub
commit d5c28c46f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,51 +0,0 @@
A TESTER
move mentions user to redux for opengroups
* MENTIONS opengroupv2
lokiPublicChatAPI
DELETED
LokiPushNotificationServerApi
channelAPI.sendMessage
dot_net_api
loki_public_chat_api
tokenlessFileServerAdnAPI
channelId
publicChat with only a cahnnel of 1
loki_file_server_api
LokiAppDotNetApi
LokiPublicChatFactoryAPI
lastPublicMessage
getLastRetrievedMessage
setLastRetrievedMessage
attemptConnection
attemptConnectionOneAtATime
getPublicSendData
setPublicSource
getPublicSource
findOrCreateServer
initAPIs
OpenGroup.
findOrCreateChannel
initSpecialConversations
updateOpenGroupV1
partChannel
acceptOpenGroupInvitationV1
pollForChannelOnce
publicConversatio
handleOpenGroupJoinV1
setChannelName
setChannelAvatar
isOpenGroupV1
toOpenGroupV1
setListOfMembers
lokiFileServerAPI
lokiFileServerAPIFactory

@ -1143,10 +1143,14 @@
"description": "Label underneath number a user enters that is not an existing contact" "description": "Label underneath number a user enters that is not an existing contact"
}, },
"invalidNumberError": { "invalidNumberError": {
"message": "Invalid public key", "message": "Invalid Session ID or ONS Name",
"description": "When a person inputs a public key that is invalid", "description": "When a person inputs a session ID or an ons name that is invalid ",
"androidKey": "fragment_new_conversation_invalid_public_key_message" "androidKey": "fragment_new_conversation_invalid_public_key_message"
}, },
"failedResolveOns": {
"message": "Failed to resolve ONS name",
"description": "When a person inputs a an ons name that is not resolved "
},
"successUnlinked": { "successUnlinked": {
"message": "Your device was unlinked successfully", "message": "Your device was unlinked successfully",
"androidKey": "activity_landing_device_unlinked_dialog_title" "androidKey": "activity_landing_device_unlinked_dialog_title"
@ -1603,17 +1607,7 @@
"message": "Invalid Pubkey Format", "message": "Invalid Pubkey Format",
"description": "Error string shown when user types an invalid pubkey format" "description": "Error string shown when user types an invalid pubkey format"
}, },
"lnsMappingNotFound": {
"message": "There is no LNS mapping associated with this name",
"description": "Shown in toast if user enters an unknown LNS name"
},
"lnsLookupTimeout": {
"message": "LNS lookup timed out",
"description": "Shown in toast if user enters an unknown LNS name"
},
"lnsTooFewNodes": {
"message": "Not enough nodes currently active for LNS lookup"
},
"emptyGroupNameError": { "emptyGroupNameError": {
"message": "Please enter a group name", "message": "Please enter a group name",
"description": "Error message displayed on empty group name", "description": "Error message displayed on empty group name",
@ -1750,8 +1744,11 @@
"message": "Enter Session ID", "message": "Enter Session ID",
"androidKey": "activity_link_device_enter_session_id_tab_title" "androidKey": "activity_link_device_enter_session_id_tab_title"
}, },
"enterSessionIDOrONSName": {
"message": "Enter Session ID or ONS name"
},
"enterSessionIDOfRecipient": { "enterSessionIDOfRecipient": {
"message": "Enter Session ID of recipient", "message": "Enter Session ID or ONS name of recipient",
"androidKey": "fragment_enter_public_key_edit_text_hint" "androidKey": "fragment_enter_public_key_edit_text_hint"
}, },
"usersCanShareTheir...": { "usersCanShareTheir...": {

@ -1351,17 +1351,7 @@
"message": "Format de clé publique non valide", "message": "Format de clé publique non valide",
"description": "Error string shown when user types an invalid pubkey format" "description": "Error string shown when user types an invalid pubkey format"
}, },
"lnsMappingNotFound": {
"message": "Aucun mappage LNS n'est associé à ce nom",
"description": "Shown in toast if user enters an unknown LNS name"
},
"lnsLookupTimeout": {
"message": "La recherche LNS a expiré",
"description": "Shown in toast if user enters an unknown LNS name"
},
"lnsTooFewNodes": {
"message": "Il n'y a pas assez de nœuds actifs actuellement pour la recherche LNS"
},
"editProfileModalTitle": { "editProfileModalTitle": {
"message": "Profil", "message": "Profil",
"description": "Title for the Edit Profile modal" "description": "Title for the Edit Profile modal"

@ -1347,17 +1347,6 @@
"message": "Invalid Pubkey Format", "message": "Invalid Pubkey Format",
"description": "Error string shown when user types an invalid pubkey format" "description": "Error string shown when user types an invalid pubkey format"
}, },
"lnsMappingNotFound": {
"message": "There is no LNS mapping associated with this name",
"description": "Shown in toast if user enters an unknown LNS name"
},
"lnsLookupTimeout": {
"message": "LNS lookup timed out",
"description": "Shown in toast if user enters an unknown LNS name"
},
"lnsTooFewNodes": {
"message": "Not enough nodes currently active for LNS lookup"
},
"editProfileModalTitle": { "editProfileModalTitle": {
"message": "Аккаунт", "message": "Аккаунт",
"description": "Title for the Edit Profile modal" "description": "Title for the Edit Profile modal"

@ -1,121 +0,0 @@
/* eslint-disable class-methods-use-this */
/* global window, Buffer, StringView, dcodeIO */
class LokiSnodeAPI {
// ************** NOTE ***************
// This is not used by anything yet,
// but should be. Do not remove!!!
// ***********************************
async getLnsMapping(lnsName, timeout) {
// Returns { pubkey, error }
// pubkey is
// undefined when unconfirmed or no mapping found
// string when found
// timeout parameter optional (ms)
// How many nodes to fetch data from?
const numRequests = 5;
// How many nodes must have the same response value?
const numRequiredConfirms = 3;
let ciphertextHex;
let pubkey;
let error;
const _ = window.Lodash;
const input = Buffer.from(lnsName);
const output = await window.blake2b(input);
const nameHash = dcodeIO.ByteBuffer.wrap(output).toString('base64');
// Timeouts
const maxTimeoutVal = 2 ** 31 - 1;
const timeoutPromise = () =>
new Promise((_resolve, reject) => setTimeout(() => reject(), timeout || maxTimeoutVal));
// Get nodes capable of doing LNS
const lnsNodes = await window.SnodePool.getNodesMinVersion(
window.CONSTANTS.LNS_CAPABLE_NODES_VERSION
);
// Enough nodes?
if (lnsNodes.length < numRequiredConfirms) {
error = { lnsTooFewNodes: window.i18n('lnsTooFewNodes') };
return { pubkey, error };
}
const confirmedNodes = [];
// Promise is only resolved when a consensus is found
let cipherResolve;
const cipherPromise = () =>
new Promise(resolve => {
cipherResolve = resolve;
});
const decryptHex = async cipherHex => {
const ciphertext = new Uint8Array(StringView.hexToArrayBuffer(cipherHex));
const res = await window.decryptLnsEntry(lnsName, ciphertext);
const publicKey = StringView.arrayBufferToHex(res);
return publicKey;
};
const fetchFromNode = async node => {
const res = await window.NewSnodeAPI._requestLnsMapping(node, nameHash);
// Do validation
if (res && res.result && res.result.status === 'OK') {
const hasMapping = res.result.entries && res.result.entries.length > 0;
const resValue = hasMapping ? res.result.entries[0].encrypted_value : null;
confirmedNodes.push(resValue);
if (confirmedNodes.length >= numRequiredConfirms) {
if (ciphertextHex) {
// Result already found, dont worry
return;
}
const [winner, count] = _.maxBy(_.entries(_.countBy(confirmedNodes)), x => x[1]);
if (count >= numRequiredConfirms) {
ciphertextHex = winner === String(null) ? null : winner;
// null represents no LNS mapping
if (ciphertextHex === null) {
error = { lnsMappingNotFound: window.i18n('lnsMappingNotFound') };
}
cipherResolve({ ciphertextHex });
}
}
}
};
const nodes = lnsNodes.splice(0, numRequests);
// Start fetching from nodes
nodes.forEach(node => fetchFromNode(node));
// Timeouts (optional parameter)
// Wait for cipher to be found; race against timeout
// eslint-disable-next-line more/no-then
await Promise.race([cipherPromise, timeoutPromise].map(f => f()))
.then(async () => {
if (ciphertextHex !== null) {
pubkey = await decryptHex(ciphertextHex);
}
})
.catch(() => {
error = { lnsLookupTimeout: window.i18n('lnsLookupTimeout') };
});
return { pubkey, error };
}
}
module.exports = LokiSnodeAPI;

@ -27,6 +27,8 @@ import { ConversationTypeEnum } from '../../models/conversation';
import { openGroupV2CompleteURLRegex } from '../../opengroup/utils/OpenGroupUtils'; import { openGroupV2CompleteURLRegex } from '../../opengroup/utils/OpenGroupUtils';
import { joinOpenGroupV2WithUIEvents } from '../../opengroup/opengroupV2/JoinOpenGroupV2'; import { joinOpenGroupV2WithUIEvents } from '../../opengroup/opengroupV2/JoinOpenGroupV2';
import autoBind from 'auto-bind'; import autoBind from 'auto-bind';
import { onsNameRegex } from '../../session/snode_api/SNodeAPI';
import { SNodeAPI } from '../../session/snode_api';
export interface Props { export interface Props {
searchTerm: string; searchTerm: string;
@ -276,6 +278,7 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
onButtonClick={this.handleMessageButtonClick} onButtonClick={this.handleMessageButtonClick}
searchTerm={searchTerm} searchTerm={searchTerm}
searchResults={searchResults} searchResults={searchResults}
showSpinner={loading}
updateSearch={this.updateSearch} updateSearch={this.updateSearch}
theme={this.props.theme} theme={this.props.theme}
/> />
@ -339,23 +342,48 @@ export class LeftPaneMessageSection extends React.Component<Props, State> {
const { openConversationExternal } = this.props; const { openConversationExternal } = this.props;
if (!this.state.valuePasted && !this.props.searchTerm) { if (!this.state.valuePasted && !this.props.searchTerm) {
ToastUtils.pushToastError('invalidPubKey', window.i18n('invalidNumberError')); ToastUtils.pushToastError('invalidPubKey', window.i18n('invalidNumberError')); // or ons name
return; return;
} }
let pubkey: string; let pubkeyorOns: string;
pubkey = this.state.valuePasted || this.props.searchTerm; pubkeyorOns = this.state.valuePasted || this.props.searchTerm;
pubkey = pubkey.trim(); pubkeyorOns = pubkeyorOns.trim();
const error = PubKey.validateWithError(pubkey); const errorOnPubkey = PubKey.validateWithError(pubkeyorOns);
if (!error) { if (!errorOnPubkey) {
// this is a pubkey
await ConversationController.getInstance().getOrCreateAndWait( await ConversationController.getInstance().getOrCreateAndWait(
pubkey, pubkeyorOns,
ConversationTypeEnum.PRIVATE ConversationTypeEnum.PRIVATE
); );
openConversationExternal(pubkey); openConversationExternal(pubkeyorOns);
this.handleToggleOverlay(undefined); this.handleToggleOverlay(undefined);
} else { } else {
ToastUtils.pushToastError('invalidPubKey', error); // this might be an ONS, validate the regex first
const mightBeOnsName = new RegExp(onsNameRegex, 'g').test(pubkeyorOns);
if (!mightBeOnsName) {
ToastUtils.pushToastError('invalidPubKey', window.i18n('invalidNumberError'));
return;
}
this.setState({ loading: true });
try {
const resolvedSessionID = await SNodeAPI.getSessionIDForOnsName(pubkeyorOns);
if (PubKey.validateWithError(resolvedSessionID)) {
throw new Error('Got a resolved ONS but the returned entry is not a vlaid SessionID');
}
// this is a pubkey
await ConversationController.getInstance().getOrCreateAndWait(
resolvedSessionID,
ConversationTypeEnum.PRIVATE
);
openConversationExternal(resolvedSessionID);
this.handleToggleOverlay(undefined);
} catch (e) {
window?.log?.warn('failed to resolve ons name', pubkeyorOns, e);
ToastUtils.pushToastError('invalidPubKey', window.i18n('failedResolveOns'));
} finally {
this.setState({ loading: false });
}
} }
} }

@ -129,7 +129,7 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
title = window.i18n('newSession'); title = window.i18n('newSession');
buttonText = window.i18n('next'); buttonText = window.i18n('next');
descriptionLong = window.i18n('usersCanShareTheir...'); descriptionLong = window.i18n('usersCanShareTheir...');
subtitle = window.i18n('enterSessionID'); subtitle = window.i18n('enterSessionIDOrONSName');
placeholder = window.i18n('enterSessionIDOfRecipient'); placeholder = window.i18n('enterSessionIDOfRecipient');
break; break;
case 'open-group': case 'open-group':
@ -192,7 +192,7 @@ export class SessionClosableOverlay extends React.Component<Props, State> {
) : ( ) : (
<SessionIdEditable <SessionIdEditable
ref={this.inputRef} ref={this.inputRef}
editable={true} editable={!showLoadingSpinner}
placeholder={placeholder} placeholder={placeholder}
onChange={onChangeSessionID} onChange={onChangeSessionID}
/> />

@ -1,4 +1,4 @@
import React, { useContext, useEffect, useReducer, useState } from 'react'; import React, { useEffect } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { import {
joinOpenGroupV2WithUIEvents, joinOpenGroupV2WithUIEvents,
@ -11,6 +11,7 @@ import { Avatar, AvatarSize } from '../Avatar';
import { Flex } from '../basic/Flex'; import { Flex } from '../basic/Flex';
import { PillContainer } from '../basic/PillContainer'; import { PillContainer } from '../basic/PillContainer';
import { H3 } from '../basic/Text'; import { H3 } from '../basic/Text';
import { SessionSpinner } from './SessionSpinner';
// tslint:disable: no-void-expression // tslint:disable: no-void-expression
export type JoinableRoomProps = { export type JoinableRoomProps = {
@ -83,29 +84,35 @@ const SessionJoinableRoomRow = (props: JoinableRoomProps) => {
export const SessionJoinableRooms = () => { export const SessionJoinableRooms = () => {
const joinableRooms = useSelector((state: StateType) => state.defaultRooms); const joinableRooms = useSelector((state: StateType) => state.defaultRooms);
if (!joinableRooms?.length) { if (!joinableRooms.inProgress && !joinableRooms.rooms?.length) {
window?.log?.info('no default joinable rooms yet'); window?.log?.info('no default joinable rooms yet and not in progress');
return <></>; return <></>;
} }
const componentToRender = joinableRooms.inProgress ? (
<SessionSpinner loading={true} />
) : (
joinableRooms.rooms.map(r => {
return (
<SessionJoinableRoomRow
key={r.id}
completeUrl={r.completeUrl}
name={r.name}
roomId={r.id}
base64Data={r.base64Data}
onClick={completeUrl => {
void joinOpenGroupV2WithUIEvents(completeUrl, true, false);
}}
/>
);
})
);
return ( return (
<Flex container={true} flexGrow={1} flexDirection="column" width="93%"> <Flex container={true} flexGrow={1} flexDirection="column" width="93%">
<H3 text={window.i18n('orJoinOneOfThese')} /> <H3 text={window.i18n('orJoinOneOfThese')} />
<Flex container={true} flexGrow={1} flexWrap="wrap"> <Flex container={true} flexGrow={1} flexWrap="wrap" justifyContent="center">
{joinableRooms.map(r => { {componentToRender}
return (
<SessionJoinableRoomRow
key={r.id}
completeUrl={r.completeUrl}
name={r.name}
roomId={r.id}
base64Data={r.base64Data}
onClick={completeUrl => {
void joinOpenGroupV2WithUIEvents(completeUrl, true, false);
}}
/>
);
})}
</Flex> </Flex>
</Flex> </Flex>
); );

10
ts/global.d.ts vendored

@ -1,10 +0,0 @@
interface Promise<T> {
ignore(): void;
}
// Types also correspond to messages.json keys
enum LnsLookupErrorType {
lnsTooFewNodes,
lnsLookupTimeout,
lnsMappingNotFound,
}

@ -45,27 +45,34 @@ export async function getAuthToken({
return roomDetails.token; return roomDetails.token;
} }
await allowOnlyOneAtATime(`getAuthTokenV2${serverUrl}:${roomId}`, async () => { await allowOnlyOneAtATime(`getAuthToken${serverUrl}:${roomId}`, async () => {
try { try {
window?.log?.info('TRIGGERING NEW AUTH TOKEN WITH', { serverUrl, roomId }); window?.log?.info(
`Triggering getAuthToken with serverUrl:'${serverUrl}'; roomId:'${roomId}'`
);
const token = await requestNewAuthToken({ serverUrl, roomId }); const token = await requestNewAuthToken({ serverUrl, roomId });
if (!token) { if (!token) {
window?.log?.warn('invalid new auth token', token); window?.log?.warn('invalid new auth token', token);
return; return;
} }
window?.log?.info(`Got AuthToken for serverUrl:'${serverUrl}'; roomId:'${roomId}'`);
const claimedToken = await claimAuthToken(token, serverUrl, roomId); const claimedToken = await claimAuthToken(token, serverUrl, roomId);
if (!claimedToken) { if (!claimedToken) {
window?.log?.warn('invalid claimed token', claimedToken); window?.log?.warn('Failed to claim token', claimedToken);
} else {
window?.log?.info(`Claimed AuthToken for serverUrl:'${serverUrl}'; roomId:'${roomId}'`);
} }
// still save it to the db. just to mark it as to be refreshed later // still save it to the db. just to mark it as to be refreshed later
roomDetails.token = claimedToken || ''; roomDetails.token = claimedToken || '';
await saveV2OpenGroupRoom(roomDetails); await saveV2OpenGroupRoom(roomDetails);
window?.log?.info(`AuthToken saved to DB for serverUrl:'${serverUrl}'; roomId:'${roomId}'`);
} catch (e) { } catch (e) {
window?.log?.error('Failed to getAuthToken', e); window?.log?.error('Failed to getAuthToken', e);
throw e; throw e;
} }
}); });
// fetch the data from the db again, which should have been written in the saveV2OpenGroupRoom() call above
const refreshedRoomDetails = await getV2OpenGroupRoomByRoomId({ const refreshedRoomDetails = await getV2OpenGroupRoomByRoomId({
serverUrl, serverUrl,
roomId, roomId,

@ -3,7 +3,7 @@ import { FileServerV2Request } from '../../fileserver/FileServerApiV2';
import { PubKey } from '../../session/types'; import { PubKey } from '../../session/types';
import { allowOnlyOneAtATime } from '../../session/utils/Promise'; import { allowOnlyOneAtATime } from '../../session/utils/Promise';
import { fromBase64ToArrayBuffer, fromHex } from '../../session/utils/String'; import { fromBase64ToArrayBuffer, fromHex } from '../../session/utils/String';
import { updateDefaultRooms } from '../../state/ducks/defaultRooms'; import { updateDefaultRooms, updateDefaultRoomsInProgress } from '../../state/ducks/defaultRooms';
import { getCompleteUrlFromRoom } from '../utils/OpenGroupUtils'; import { getCompleteUrlFromRoom } from '../utils/OpenGroupUtils';
import { parseOpenGroupV2 } from './JoinOpenGroupV2'; import { parseOpenGroupV2 } from './JoinOpenGroupV2';
import { getAllRoomInfos } from './OpenGroupAPIV2'; import { getAllRoomInfos } from './OpenGroupAPIV2';
@ -120,7 +120,10 @@ const loadDefaultRoomsSingle = () =>
* This call will only run once at a time. * This call will only run once at a time.
*/ */
export const loadDefaultRooms = async () => { export const loadDefaultRooms = async () => {
window.inboxStore?.dispatch(updateDefaultRoomsInProgress(true));
const allRooms: Array<OpenGroupV2InfoJoinable> = await loadDefaultRoomsSingle(); const allRooms: Array<OpenGroupV2InfoJoinable> = await loadDefaultRoomsSingle();
window.inboxStore?.dispatch(updateDefaultRoomsInProgress(false));
if (allRooms !== undefined) { if (allRooms !== undefined) {
window.inboxStore?.dispatch(updateDefaultRooms(allRooms)); window.inboxStore?.dispatch(updateDefaultRooms(allRooms));
} }

@ -13,9 +13,20 @@ import { snodeRpc } from './lokiRpc';
import { getRandomSnode, getRandomSnodePool, requiredSnodesForAgreement, Snode } from './snodePool'; import { getRandomSnode, getRandomSnodePool, requiredSnodesForAgreement, Snode } from './snodePool';
import { Constants } from '..'; import { Constants } from '..';
import { sha256 } from '../crypto'; import { getSodium, sha256 } from '../crypto';
import _ from 'lodash'; import _, { range } from 'lodash';
import pRetry from 'p-retry'; import pRetry from 'p-retry';
import {
fromHex,
fromHexToArray,
fromUInt8ArrayToBase64,
stringToUint8Array,
toHex,
} from '../utils/String';
// ONS name can have [a-zA-Z0-9_-] except that - is not allowed as start or end
// do not define a regex but rather create it on the fly to avoid https://stackoverflow.com/questions/3891641/regex-test-only-works-every-other-time
export const onsNameRegex = '^[a-zA-Z0-9_][a-zA-Z0-9_-]*[a-zA-Z0-9_]$';
const getSslAgentForSeedNode = (seedNodeHost: string, isSsl = false) => { const getSslAgentForSeedNode = (seedNodeHost: string, isSsl = false) => {
let filePrefix = ''; let filePrefix = '';
@ -247,20 +258,127 @@ export async function requestSnodesForPubkey(pubKey: string): Promise<Array<Snod
} }
} }
export async function requestLnsMapping(targetNode: Snode, nameHash: any) { export async function getSessionIDForOnsName(onsNameCase: string) {
window?.log?.debug('[lns] lns requests to {}:{}', targetNode.ip, targetNode); const validationCount = 3;
try {
// TODO: Check response status const onsNameLowerCase = onsNameCase.toLowerCase();
return snodeRpc( const sodium = await getSodium();
'get_lns_mapping', const nameAsData = stringToUint8Array(onsNameLowerCase);
{ const nameHash = sodium.crypto_generichash(sodium.crypto_generichash_BYTES, nameAsData);
name_hash: nameHash, const base64EncodedNameHash = fromUInt8ArrayToBase64(nameHash);
},
targetNode const params = {
endpoint: 'ons_resolve',
params: {
type: 0,
name_hash: base64EncodedNameHash,
},
};
// we do this request with validationCount snodes
const promises = range(0, validationCount).map(async () => {
const targetNode = await getRandomSnode();
const result = await snodeRpc('oxend_request', params, targetNode);
if (!result || result.status !== 200 || !result.body) {
throw new Error('ONSresolve:Failed to resolve ONS');
}
let parsedBody;
try {
parsedBody = JSON.parse(result.body);
} catch (e) {
window?.log?.warn('ONSresolve: failed to parse ons result body', result.body);
throw new Error('ONSresolve: json ONS resovle');
}
const intermediate = parsedBody?.result;
if (!intermediate || !intermediate?.encrypted_value) {
throw new Error('ONSresolve: no encrypted_value');
}
const hexEncodedCipherText = intermediate?.encrypted_value;
const isArgon2Based = !Boolean(intermediate?.nonce);
const ciphertext = fromHexToArray(hexEncodedCipherText);
let sessionIDAsData: Uint8Array;
let nonce: Uint8Array;
let key: Uint8Array;
if (isArgon2Based) {
// Handle old Argon2-based encryption used before HF16
const salt = new Uint8Array(sodium.crypto_pwhash_SALTBYTES);
nonce = new Uint8Array(sodium.crypto_secretbox_NONCEBYTES);
try {
const keyHex = sodium.crypto_pwhash(
sodium.crypto_secretbox_KEYBYTES,
onsNameLowerCase,
salt,
sodium.crypto_pwhash_OPSLIMIT_MODERATE,
sodium.crypto_pwhash_MEMLIMIT_MODERATE,
sodium.crypto_pwhash_ALG_ARGON2ID13,
'hex'
);
if (!keyHex) {
throw new Error('ONSresolve: key invalid argon2');
}
key = fromHexToArray(keyHex);
} catch (e) {
throw new Error('ONSresolve: Hashing failed');
}
sessionIDAsData = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
if (!sessionIDAsData) {
throw new Error('ONSresolve: Decryption failed');
}
return toHex(sessionIDAsData);
}
// not argon2Based
const hexEncodedNonce = intermediate.nonce as string;
if (!hexEncodedNonce) {
throw new Error('ONSresolve: No hexEncodedNonce');
}
nonce = fromHexToArray(hexEncodedNonce);
try {
key = sodium.crypto_generichash(sodium.crypto_generichash_BYTES, nameAsData, nameHash);
if (!key) {
throw new Error('ONSresolve: Hashing failed');
}
} catch (e) {
window?.log?.warn('ONSresolve: hashing failed', e);
throw new Error('ONSresolve: Hashing failed');
}
sessionIDAsData = sodium.crypto_aead_xchacha20poly1305_ietf_decrypt(
null,
ciphertext,
null,
nonce,
key
); );
if (!sessionIDAsData) {
throw new Error('ONSresolve: Decryption failed');
}
return toHex(sessionIDAsData);
});
try {
// if one promise throws, we end un the catch case
const allResolvedSessionIds = await Promise.all(promises);
if (allResolvedSessionIds?.length !== validationCount) {
throw new Error('ONSresolve: Validation failed');
}
// assert all the returned session ids are the same
if (_.uniq(allResolvedSessionIds).length !== 1) {
throw new Error('ONSresolve: Validation failed');
}
return allResolvedSessionIds[0];
} catch (e) { } catch (e) {
window?.log?.warn('exception caught making lns requests to a node', targetNode, e); window.log.warn('ONSresolve: error', e);
return false; throw e;
} }
} }

@ -67,7 +67,7 @@ async function lokiFetch(
* This function will throw for a few reasons. * This function will throw for a few reasons.
* The loki-important ones are * The loki-important ones are
* -> if we try to make a request to a path which fails too many times => user will need to retry himself * -> if we try to make a request to a path which fails too many times => user will need to retry himself
* -> if the targetNode gets too many errors => we will need to try do to this request again with anoter target node * -> if the targetNode gets too many errors => we will need to try to do this request again with another target node
* The * The
*/ */
export async function snodeRpc( export async function snodeRpc(

@ -33,10 +33,10 @@ export function nonNullish<V>(v: V): v is NonNullable<V> {
export const toHex = (d: BufferType) => decode(d, 'hex'); export const toHex = (d: BufferType) => decode(d, 'hex');
export const fromHex = (d: string) => encode(d, 'hex'); export const fromHex = (d: string) => encode(d, 'hex');
export const fromHexToArray = (d: string) => new Uint8Array(encode(d, 'hex')); export const fromHexToArray = (d: string) => new Uint8Array(fromHex(d));
export const fromBase64ToArrayBuffer = (d: string) => encode(d, 'base64'); export const fromBase64ToArrayBuffer = (d: string) => encode(d, 'base64');
export const fromBase64ToArray = (d: string) => new Uint8Array(encode(d, 'base64')); export const fromBase64ToArray = (d: string) => new Uint8Array(fromBase64ToArrayBuffer(d));
export const fromArrayBufferToBase64 = (d: BufferType) => decode(d, 'base64'); export const fromArrayBufferToBase64 = (d: BufferType) => decode(d, 'base64');
export const fromUInt8ArrayToBase64 = (d: Uint8Array) => decode(d, 'base64'); export const fromUInt8ArrayToBase64 = (d: Uint8Array) => decode(d, 'base64');
@ -48,3 +48,7 @@ export const stringToArrayBuffer = (str: string): ArrayBuffer => {
return encode(str, 'binary'); return encode(str, 'binary');
}; };
export const stringToUint8Array = (str: string): Uint8Array => {
return new Uint8Array(stringToArrayBuffer(str));
};

@ -1,9 +1,15 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { OpenGroupV2InfoJoinable } from '../../opengroup/opengroupV2/ApiUtil'; import { OpenGroupV2InfoJoinable } from '../../opengroup/opengroupV2/ApiUtil';
export type DefaultRoomsState = Array<OpenGroupV2InfoJoinable>; export type DefaultRoomsState = {
rooms: Array<OpenGroupV2InfoJoinable>;
inProgress: boolean;
};
const initialState: DefaultRoomsState = []; const initialState: DefaultRoomsState = {
rooms: [],
inProgress: false,
};
/** /**
* Payload to dispatch to update the base64 data of a default room * Payload to dispatch to update the base64 data of a default room
@ -21,12 +27,18 @@ const defaultRoomsSlice = createSlice({
initialState, initialState,
reducers: { reducers: {
updateDefaultRooms(state, action) { updateDefaultRooms(state, action) {
window?.log?.warn('updating default rooms', action.payload); window?.log?.info('updating default rooms', action.payload);
return action.payload as DefaultRoomsState; const rooms = action.payload as Array<OpenGroupV2InfoJoinable>;
return { ...state, rooms: rooms };
},
updateDefaultRoomsInProgress(state, action) {
const inProgress = action.payload as boolean;
window?.log?.info('fetching default rooms inProgress?', action.payload);
return { ...state, inProgress };
}, },
updateDefaultBase64RoomData(state, action: PayloadAction<Base64Update>) { updateDefaultBase64RoomData(state, action: PayloadAction<Base64Update>) {
const payload = action.payload; const payload = action.payload;
const newState = state.map(room => { const newRoomsState = state.rooms.map(room => {
if (room.id === payload.roomId) { if (room.id === payload.roomId) {
return { return {
...room, ...room,
@ -35,11 +47,15 @@ const defaultRoomsSlice = createSlice({
} }
return room; return room;
}); });
return newState; return { ...state, rooms: newRoomsState };
}, },
}, },
}); });
const { actions, reducer } = defaultRoomsSlice; const { actions, reducer } = defaultRoomsSlice;
export const { updateDefaultRooms, updateDefaultBase64RoomData } = actions; export const {
updateDefaultRooms,
updateDefaultBase64RoomData,
updateDefaultRoomsInProgress,
} = actions;
export const defaultRoomReducer = reducer; export const defaultRoomReducer = reducer;

Loading…
Cancel
Save