Merge remote-tracking branch 'upstream/clearnet' into voice-message-record-mp3

This commit is contained in:
Audric Ackermann 2021-04-20 12:57:47 +10:00
commit 2e29206c99
No known key found for this signature in database
GPG Key ID: 999F434D76324AD4
43 changed files with 539 additions and 283 deletions

View File

@ -1,39 +0,0 @@
indexeddb
remove from db
* 'sentSessionsTimestamp'
* 'processedSessionsTimestamp'
* 'sessions'
* 'preKeys'
* 'signedPreKeys'
* senderkeys
getContact()
remove what is is Storage / user.js
remove on the UI ts files the calls to conversationModel. everything should be on the props
conversationModel
.get()
getOurNumber
primaryDevicePubKey
getRecipients() does not make asny sense right
ReadSyncs
SyncMessage
sendSyncMessage needs to be rewritten
sendSyncMessageOnly to fix
indexedDB
initializeAttachmentMetadata=>
run_migration
### Bug fixes on update of models
* quote of attachment does not share preview
* setting disappearing timer for isMe does not trigger the message
* expiration timer set from user1 second device is not synced to his other devices for a private chat
* add a way for users to know when the messageQueue is reconnected (loading bar or something)
* handled better reconnection

View File

@ -11,7 +11,7 @@ for it or creating a new one yourself. You can use also that issue as a place to
your intentions and get feedback from the users most likely to appreciate your changes.
You're most likely to have your pull request accepted easily if it addresses bugs already
in the [Next Steps project](https://github.com/loki-project/session-desktop/projects/1),
in the [Next Steps project](https://github.com/oxen-io/session-desktop/projects/1),
especially if they are near the top of the Backlog column. Those are what we'll be looking
at next, so it would be great if you helped us out!
@ -22,7 +22,7 @@ ounce of prevention, as they say!](https://www.goodreads.com/quotes/247269-an-ou
## Developer Setup
First, you'll need [Node.js](https://nodejs.org/) which matches our current version.
You can check [`.nvmrc` in the `development` branch](https://github.com/loki-project/session-desktop/blob/development/.nvmrc) to see what the current version is. If you have [nvm](https://github.com/creationix/nvm)
You can check [`.nvmrc` in the `clearnet` branch](https://github.com/oxen-io/session-desktop/blob/clearnet/.nvmrc) to see what the current version is. If you have [nvm](https://github.com/creationix/nvm)
you can just run `nvm use` in the project directory and it will switch to the project's
desired Node.js version. [nvm for windows](https://github.com/coreybutler/nvm-windows) is
still useful, but it doesn't support `.nvmrc` files.
@ -56,7 +56,7 @@ Then you need `git`, if you don't have that yet: https://git-scm.com/
Now, run these commands in your preferred terminal in a good directory for development:
```
git clone https://github.com/loki-project/session-desktop.git
git clone https://github.com/oxen-io/session-desktop.git
cd session-desktop
npm install --global yarn # (only if you dont already have `yarn`)
yarn install --frozen-lockfile # Install and build dependencies (this will take a while)

View File

@ -2,13 +2,13 @@
## Summary
Session integrates directly with [Loki Service Nodes](https://lokidocs.com/ServiceNodes/SNOverview/), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users IP Addresses. For a full understanding of how Session works, read the [Session Whitepaper](https://getsession.org/whitepaper).
Session integrates directly with [Oxen Service Nodes](https://docs.oxen.io/about-the-oxen-blockchain/oxen-service-nodes), which are a set of distributed, decentralized and Sybil resistant nodes. Service Nodes act as servers which store messages offline, and a set of nodes which allow for onion routing functionality obfuscating users IP Addresses. For a full understanding of how Session works, read the [Session Whitepaper](https://getsession.org/whitepaper).
<br/><br/>
![DesktopSession](https://i.imgur.com/ZnHvYjo.jpg)
## Want to Contribute? Found a Bug or Have a feature request?
Please search for any [existing issues](https://github.com/loki-project/session-desktop/issues) that describe your bugs in order to avoid duplicate submissions. <br><br>Submissions can be made by making a pull request to our development branch. If you don't know where to start contributing, try reading the Github issues page for ideas.
Please search for any [existing issues](https://github.com/oxen-io/session-desktop/issues) that describe your bugs in order to avoid duplicate submissions. <br><br>Submissions can be made by making a pull request to our development branch. If you don't know where to start contributing, try reading the Github issues page for ideas.
## Build instruction

View File

@ -453,6 +453,10 @@
"unverify": {
"message": "Mark As Not Verified"
},
"markAllAsRead": {
"message": "Mark All as Read",
"description": "Shown on a menu to mark the whole convo as read."
},
"isNotVerified": {
"message": "You have not verified your safety number with $name$.",
"description": "Summary state shown at top of the safety number screen if user has not verified contact.",
@ -1062,6 +1066,10 @@
"description": "Copy to clipboard session ID",
"androidKey": "activity_conversation_menu_copy_session_id"
},
"copyOpenGroupURL": {
"message": "Copy Group's URL",
"description": "Copy to clipboard Open Group URL"
},
"save": {
"message": "Save",
"description": "Used as a 'commit changes' button in the Caption Editor for outgoing image attachments",

View File

@ -363,6 +363,10 @@
}
}
},
"markAllAsRead": {
"message": "Tout Marquer Comme Lu",
"description": "Shown on a menu to mark the whole convo as read."
},
"isNotVerified": {
"message": "Vous navez pas vérifié votre numéro de sécurité avec $name$",
"description": "Summary state shown at top of the safety number screen if user has not verified contact.",
@ -1221,6 +1225,14 @@
"copy": {
"message": "Copier"
},
"copySessionID": {
"message": "Copier le Session ID",
"description": "Copy to clipboard session ID"
},
"copyOpenGroupURL": {
"message": "Copier l'URL de Group",
"description": "Copy to clipboard Open Group URL"
},
"linkPreviewsTitle": {
"message": "Envoyer des aperçus de liens"
},

View File

@ -33,17 +33,18 @@
</style>
</head>
<body>
<img src='images/session/session_icon_64.png' width='250' height='250'>
<img src='images/session/session_icon.png' width='250' height='250'>
<div class='version'></div>
<div class='commitHash'></div>
<div class='environment'></div>
<div>
<a href="https://getsession.org">loki.network</a>
<a href="https://getsession.org">getsession.org</a>
</div>
<br>
<div>
<a class="privacy" href="https://getsession.org">Terms &amp; Privacy Policy</a>
<a class="privacy" href="https://getsession.org/privacy-policy">Privacy Policy</a><br />
<a class="privacy" href="https://getsession.org/terms-of-service/">Terms of Service</a>
</div>
<script type='text/javascript' src='node_modules/jquery/dist/jquery.js'></script>

View File

@ -23,7 +23,6 @@ if (environment === 'production') {
process.env.HOSTNAME = '';
process.env.ALLOW_CONFIG_MUTATIONS = '';
process.env.SUPPRESS_NO_CONFIG_WARNING = '';
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '';
// We could be running againt production but still be in dev mode, we need to handle that
if (!isDevelopment) {

View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIIEEzCCAvugAwIBAgIUY9RQqbjhsQEkdeSgV9L0os9xZ7AwDQYJKoZIhvcNAQEL
BQAwfDELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpY3RvcmlhMRIwEAYDVQQHDAlN
ZWxib3VybmUxJTAjBgNVBAoMHE94ZW4gUHJpdmFjeSBUZWNoIEZvdW5kYXRpb24x
HzAdBgNVBAMMFnB1YmxpYy5sb2tpLmZvdW5kYXRpb24wHhcNMjEwNDA3MDExMDMx
WhcNMjMwNDA3MDExMDMxWjB8MQswCQYDVQQGEwJBVTERMA8GA1UECAwIVmljdG9y
aWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2YWN5IFRl
Y2ggRm91bmRhdGlvbjEfMB0GA1UEAwwWcHVibGljLmxva2kuZm91bmRhdGlvbjCC
ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM5dBJSIR5+VNNUxUOo6FG0e
RmZteRqBt50KXGbOi2A23a6sa57pLFh9Yw3hmlWV+QCL7ipG1X4IC55OStgoesf+
K65VwEMP6Mtq0sSJS3R5TiuV2ZSRdSZTVjUyRXVe5T4Aw6wXVTAbc/HsyS780tDh
GclfDHhonPhZpmTAnSbfMOS+BfOnBNvDxdto0kVh6k5nrGlkT4ECloulHTQF2lwJ
0D6IOtv9AJplPdg6s2c4dY7durOdvr3NNVfvn5PTeRvbEPqzZur4WUUKIPNGu6mY
PxImqd4eUsL0Vod4aAsTIx4YMmCTi0m9W6zJI6nXcK/6a+iiA3+NTNMzEA9gQhEC
AwEAAaOBjDCBiTAdBgNVHQ4EFgQU/zahokxLvvFUpbnM6z/pwS1KsvwwHwYDVR0j
BBgwFoAU/zahokxLvvFUpbnM6z/pwS1KsvwwDwYDVR0TAQH/BAUwAwEB/zAhBgNV
HREEGjAYghZwdWJsaWMubG9raS5mb3VuZGF0aW9uMBMGA1UdJQQMMAoGCCsGAQUF
BwMBMA0GCSqGSIb3DQEBCwUAA4IBAQBql+JvoqpaYrFFTOuDn08U+pdcd3GM7tbI
zRH5LU+YnIpp9aRheek+2COW8DXsIy/kUngETCMLmX6ZaUj/WdHnTDkB0KTgxSHv
ad3ZznKPKZ26qJOklr+0ZWj4J3jHbisSzql6mqq7R2Kp4ESwzwqxvkbykM5RUnmz
Go/3Ol7bpN/ZVwwEkGfD/5rRHf57E/gZn2pBO+zotlQgr7HKRsIXQ2hIXVQqWmPQ
lvfIwrwAZlfES7BARFnHOpyVQxV8uNcV5K5eXzuVFjHBqvq+BtyGhWkP9yKJCHS9
OUXxch0rzRsH2C/kRVVhEk0pI3qlFiRC8pCJs98SNE9l69EQtG7I
-----END CERTIFICATE-----

View File

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEITCCAwmgAwIBAgIUJsox1ZQPK/6iDsCC+MUJfNAlFuYwDQYJKoZIhvcNAQEL
BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ
TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u
MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQxLmxva2kubmV0d29yazAeFw0yMTA0MDcw
MTE5MjZaFw0yMzA0MDcwMTE5MjZaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI
VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2
YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMS5sb2tp
Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtWH3Rz8Dd
kEmM7tcBWHrJ/G8drr/+qidboEVYzxpyRjszaDxKXVhx4eBBsAD5RuCWuTuZmM8k
TKEDLtf8xfb5SQ7YNX+346s9NXS5Poy4CIPASiW/QWXgIHFbVdv2hC+cKOP61OLM
OGnOxfig6tQyd6EaCkedpY1DvSa2lPnQSOwC/jXCx6Vboc0zTY5R2bHtNc9hjIFP
F4VClLAQSh2F4R1V9MH5KZMW+CCP6oaJY658W9JYXYRwlLrL2EFOVxHgcxq/6+fw
+axXK9OXJrGZjuA+hiz+L/uAOtE4WuxrSeuNMHSrMtM9QqVn4bBuMJ21mAzfNoMP
OIwgMT9DwUjVAgMBAAGjgZAwgY0wHQYDVR0OBBYEFOubJp9SoXIw+ONiWgkOaW8K
zI/TMB8GA1UdIwQYMBaAFOubJp9SoXIw+ONiWgkOaW8KzI/TMA8GA1UdEwEB/wQF
MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMS5sb2tpLm5ldHdvcmswEwYD
VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAIiHNhNrjYvwXVWs
gacx8T/dpqpu9GE3L17LotgQr4R+IYHpNtcmwOTdtWWFfUTr75OCs+c3DqgRKEoj
lnULOsVcalpAGIvW15/fmZWOf66Dpa4+ljDmAc3SOQiD0gGNtqblgI5zG1HF38QP
hjYRhCZ5CVeGOLucvQ8tVVwQvArPFIkBr0jH9jHVgRWEI2MeI3FsU2H93D4TfGln
N4SmmCfYBqygaaZBWkJEt0bYhn8uGHdU9UY9L2FPtfHVKkmFgO7cASGlvXS7B/TT
/8IgbtM3O8mZc2asmdQhGwoAKz93ryyCd8X2UZJg/IwCSCayOlYZWY2fR4OPQmmV
gxJsm+g=
-----END CERTIFICATE-----

View File

@ -0,0 +1,25 @@
-----BEGIN CERTIFICATE-----
MIIEITCCAwmgAwIBAgIUc486Dy9Y00bUFfDeYmJIgSS5xREwDQYJKoZIhvcNAQEL
BQAwgYAxCzAJBgNVBAYTAkFVMREwDwYDVQQIDAhWaWN0b3JpYTESMBAGA1UEBwwJ
TWVsYm91cm5lMSUwIwYDVQQKDBxPeGVuIFByaXZhY3kgVGVjaCBGb3VuZGF0aW9u
MSMwIQYDVQQDDBpzdG9yYWdlLnNlZWQzLmxva2kubmV0d29yazAeFw0yMTA0MDcw
MTIwNTJaFw0yMzA0MDcwMTIwNTJaMIGAMQswCQYDVQQGEwJBVTERMA8GA1UECAwI
VmljdG9yaWExEjAQBgNVBAcMCU1lbGJvdXJuZTElMCMGA1UECgwcT3hlbiBQcml2
YWN5IFRlY2ggRm91bmRhdGlvbjEjMCEGA1UEAwwac3RvcmFnZS5zZWVkMy5sb2tp
Lm5ldHdvcmswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtokMlsFzf
piYeD0EVNikMyvjltpF6fUEde9NOVrTtNTQT6kkDk+/0HF5LYgPaatv6v7fpUQHi
kIwd6F0LTRGeWDFdsaWMdtlR1n/GxLPrOROsE8dcLt6GLavPf9rDabgva93m/JD6
XW+Ne+MPEwqS8dAmFGhZd0gju6AtKFoSHnIf5pSQN6fSZUF/JQtHLVprAKKWKDiS
ZwmWbmrZR2aofLD/VRpetabajnZlv9EeWloQwvUsw1C1hkAmmtFeeXtg7ePwrOzo
6CnmcUJwOmi+LWqQV4A+58RZPFKaZoC5pzaKd0OYB8eZ8HB1F41UjGJgheX5Cyl4
+amfF3l8dSq1AgMBAAGjgZAwgY0wHQYDVR0OBBYEFM9VSq4pGydjtX92Beul4+ml
jBKtMB8GA1UdIwQYMBaAFM9VSq4pGydjtX92Beul4+mljBKtMA8GA1UdEwEB/wQF
MAMBAf8wJQYDVR0RBB4wHIIac3RvcmFnZS5zZWVkMy5sb2tpLm5ldHdvcmswEwYD
VR0lBAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggEBAAYxmhhkcKE1n6g1
JqOa3UCBo4EfbqY5+FDZ0FVqv/cwemwVpKLbe6luRIS8poomdPCyMOS45V7wN3H9
cFpfJ1TW19ydPVKmCXrl29ngmnY1q7YDwE/4qi3VK/UiqDkTHMKWjVPkenOyi8u6
VVQANXSnKrn6GtigNFjGyD38O+j7AUSXBtXOJczaoF6r6BWgwQZ2WmgjuwvKTWSN
4r8uObERoAQYVaeXfgdr4e9X/JdskBDaLFfoW/rrSozHB4FqVNFW96k+aIUgRa5p
9kv115QcBPCSh9qOyTHij4tswS6SyOFaiKrNC4hgHQXP4QgioKmtsR/2Y+qJ6ddH
6oo+4QU=
-----END CERTIFICATE-----

View File

@ -6,16 +6,16 @@
"defaultPoWDifficulty": "1",
"seedNodeList": [
{
"ip_url": "http://116.203.53.213/",
"url": "https://storage.seed1.loki.network/"
"ip_url": "http://116.203.53.213:4433/",
"url": "https://storage.seed1.loki.network:4433/"
},
{
"ip_url": "http://212.199.114.66/",
"url": "https://storage.seed3.loki.network/"
"ip_url": "http://212.199.114.66:4433/",
"url": "https://storage.seed3.loki.network:4433/"
},
{
"ip_url": "http://144.76.164.202/",
"url": "https://public.loki.foundation/"
"ip_url": "http://144.76.164.202:4433/",
"url": "https://public.loki.foundation:4433/"
}
],
"updatesEnabled": false,

View File

@ -2,7 +2,7 @@
/* global window */
const FormData = require('form-data');
const fetch = require('node-fetch');
const insecureNodeFetch = require('node-fetch');
const BASE_URL = 'https://debuglogs.org';
const VERSION = window.getVersion();
@ -10,7 +10,8 @@ const USER_AGENT = `Session ${VERSION}`;
// upload :: String -> Promise URL
exports.upload = async content => {
const signedForm = await fetch(BASE_URL, {
window.log.warn('insecureNodeFetch => upload debugLogs');
const signedForm = await insecureNodeFetch(BASE_URL, {
headers: {
'user-agent': USER_AGENT,
},
@ -38,7 +39,7 @@ exports.upload = async content => {
filename: `session-desktop-debug-log-${VERSION}.txt`,
});
const result = await fetch(url, {
const result = await insecureNodeFetch(url, {
method: 'POST',
body: form,
});

View File

@ -1,7 +1,7 @@
/* global log, textsecure, libloki, Signal, Whisper,
clearTimeout, getMessageController, libsignal, StringView, window, _,
dcodeIO, Buffer, process */
const nodeFetch = require('node-fetch');
const insecureNodeFetch = require('node-fetch');
const { URL, URLSearchParams } = require('url');
const FormData = require('form-data');
const https = require('https');
@ -253,7 +253,7 @@ const serverRequest = async (endpoint, options = {}) => {
let response;
let result;
let txtResponse;
let mode = 'nodeFetch';
let mode = 'insecureNodeFetch';
try {
const host = url.host.toLowerCase();
// log.info('host', host, FILESERVER_HOSTS);
@ -268,7 +268,12 @@ const serverRequest = async (endpoint, options = {}) => {
fetchOptions,
options
));
} else if (window.lokiFeatureFlags.useFileOnionRequests && srvPubKey) {
} else if (window.lokiFeatureFlags.useFileOnionRequests) {
if (!srvPubKey) {
throw new Error(
'useFileOnionRequests=true but we do not have a server pubkey set.'
);
}
mode = 'sendViaOnionOG';
({ response, txtResponse, result } = await sendViaOnion(
srvPubKey,
@ -277,13 +282,9 @@ const serverRequest = async (endpoint, options = {}) => {
options
));
} else {
// disable check for .loki
process.env.NODE_TLS_REJECT_UNAUTHORIZED = host.match(/\.loki$/i)
? '0'
: '1';
result = await nodeFetch(url, fetchOptions);
// always make sure this check is enabled
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
// we end up here only if window.lokiFeatureFlags.useFileOnionRequests is false
log.info(`insecureNodeFetch => plaintext for ${url}`);
result = await insecureNodeFetch(url, fetchOptions);
txtResponse = await result.text();
// cloudflare timeouts (504s) will be html...
@ -1395,23 +1396,13 @@ class LokiPublicChannelAPI {
// do we already have this image? no, then
// download a copy and save it
const imageData = await nodeFetch(avatarAbsUrl);
// eslint-disable-next-line no-inner-declarations
function toArrayBuffer(buf) {
const ab = new ArrayBuffer(buf.length);
const view = new Uint8Array(ab);
// eslint-disable-next-line no-plusplus
for (let i = 0; i < buf.length; i++) {
view[i] = buf[i];
}
return ab;
}
// eslint-enable-next-line no-inner-declarations
const imageData = await this.serverAPI.downloadAttachment(
avatarAbsUrl
);
const buffer = await imageData.buffer();
const newAttributes = await window.Signal.Types.Conversation.maybeUpdateAvatar(
this.conversation.attributes,
toArrayBuffer(buffer),
imageData,
{
writeNewAttachmentData,
deleteAttachmentData,

View File

@ -41,9 +41,14 @@ async function allowOnlyOneAtATime(name, process, timeout) {
try {
innerRetVal = await process();
} catch (e) {
log.error(
`loki_primitives:::allowOnlyOneAtATime - error ${e.code} ${e.message}`
);
if (typeof e === 'string') {
log.error(`loki_primitives:::allowOnlyOneAtATime - error ${e}`);
} else {
log.error(
`loki_primitives:::allowOnlyOneAtATime - error ${e.code} ${e.message}`
);
}
// clear timeout timer
if (timeout) {
if (timeoutTimer !== null) {

View File

@ -2,78 +2,93 @@
const EventEmitter = require('events');
const LokiAppDotNetAPI = require('./loki_app_dot_net_api');
const nodeFetch = require('node-fetch');
const insecureNodeFetch = require('node-fetch');
/**
* Tries to establish a connection with the specified open group url.
*
* This will try to do an onion routing call if the `useFileOnionRequests` feature flag is set,
* or call directly insecureNodeFetch if it's not.
*
* Returns
* * true if useFileOnionRequests is false and no exception where thrown by insecureNodeFetch
* * true if useFileOnionRequests is true and we established a connection to the server with onion routing
* * false otherwise
*
*/
const validOpenGroupServer = async serverUrl => {
// test to make sure it's online (and maybe has a valid SSL cert)
try {
const url = new URL(serverUrl);
if (window.lokiFeatureFlags.useFileOnionRequests) {
// check for LSRPC
if (!window.lokiFeatureFlags.useFileOnionRequests) {
// we are not running with onion request
// this is an insecure insecureNodeFetch. It will expose the user ip to the serverUrl (not onion routed)
log.info(`insecureNodeFetch => plaintext for ${url.toString()}`);
// this is safe (as long as node's in your trust model)
// because
const result = await window.tokenlessFileServerAdnAPI.serverRequest(
`loki/v1/getOpenGroupKey/${url.hostname}`
);
if (result.response.meta.code === 200) {
// supports it
const obj = JSON.parse(result.response.data);
const pubKey = dcodeIO.ByteBuffer.wrap(
obj.data,
'base64'
).toArrayBuffer();
// verify it works...
// get around the FILESERVER_HOSTS filter by not using serverRequest
const res = await LokiAppDotNetAPI.sendViaOnion(
pubKey,
url,
{ method: 'GET' },
{ noJson: true }
);
if (res.result && res.result.status === 200) {
log.info(
`loki_public_chat::validOpenGroupServer - onion routing enabled on ${url.toString()}`
);
// save pubkey for use...
window.lokiPublicChatAPI.openGroupPubKeys[serverUrl] = pubKey;
return true;
}
// otherwise fall back
} else if (result.response.meta.code !== 404) {
// unknown error code
log.warn(
'loki_public_chat::validOpenGroupServer - unknown error code',
result.response.meta
);
}
// we probably have to check the response here
await insecureNodeFetch(serverUrl);
return true;
}
// doesn't support it, fallback
log.info(
`loki_public_chat::validOpenGroupServer - directly contacting ${url.toString()}`
// This MUST be an onion routing call, no nodeFetch calls below here.
/**
* this is safe (as long as node's in your trust model)
*
* First, we need to fetch the open group public key of this open group.
* The fileserver have all the open groups public keys.
* We need the open group public key because for onion routing we will need to encode
* our request with it.
* We can just ask the file-server to get the one for the open group we are trying to add.
*/
const result = await window.tokenlessFileServerAdnAPI.serverRequest(
`loki/v1/getOpenGroupKey/${url.hostname}`
);
// allow .loki (may only need an agent but not sure
// until we have a .loki to test with)
process.env.NODE_TLS_REJECT_UNAUTHORIZED = url.host.match(/\.loki$/i)
? '0'
: '1';
await nodeFetch(serverUrl);
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
// const txt = await res.text();
if (result.response.meta.code === 200) {
// we got the public key of the server we are trying to add.
// decode it.
const obj = JSON.parse(result.response.data);
const pubKey = dcodeIO.ByteBuffer.wrap(
obj.data,
'base64'
).toArrayBuffer();
// verify we can make an onion routed call to that open group with the decoded public key
// get around the FILESERVER_HOSTS filter by not using serverRequest
const res = await LokiAppDotNetAPI.sendViaOnion(
pubKey,
url,
{ method: 'GET' },
{ noJson: true }
);
if (res.result && res.result.status === 200) {
log.info(
`loki_public_chat::validOpenGroupServer - onion routing enabled on ${url.toString()}`
);
// save pubkey for use...
window.lokiPublicChatAPI.openGroupPubKeys[serverUrl] = pubKey;
return true;
}
// return here, just so we are sure adding some code below won't do a nodeFetch fallback
return false;
} else if (result.response.meta.code !== 404) {
// unknown error code
log.warn(
'loki_public_chat::validOpenGroupServer - unknown error code',
result.response.meta
);
}
return false;
} catch (e) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
log.warn(
`loki_public_chat::validOpenGroupServer - failing to create ${serverUrl}`,
e.code,
e.message
);
// bail out if not valid enough
return false;
}
return true;
return false;
};
class LokiPublicChatFactoryAPI extends EventEmitter {
@ -139,11 +154,11 @@ class LokiPublicChatFactoryAPI extends EventEmitter {
);
return null;
}
if (window.isDev) {
log.info(
`loki_public_chat::findOrCreateServer - set token ${thisServer.token} for ${serverUrl}`
);
}
// if (window.isDev) {
// log.info(
// `loki_public_chat::findOrCreateServer - set token ${thisServer.token} for ${serverUrl}`
// );
// }
this.servers.push(thisServer);
}

View File

@ -8,6 +8,7 @@ const { escapeRegExp } = require('lodash');
const APP_ROOT_PATH = path.join(__dirname, '..', '..', '..');
const SESSION_ID_PATTERN = /\b(05[0-9a-f]{64})\b/gi;
const SNODE_PATTERN = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const GROUP_ID_PATTERN = /(group\()([^)]+)(\))/g;
const REDACTION_PLACEHOLDER = '[REDACTED]';
@ -64,6 +65,14 @@ exports.redactSessionID = text => {
return text.replace(SESSION_ID_PATTERN, REDACTION_PLACEHOLDER);
};
exports.redactSnodeIP = text => {
if (!is.string(text)) {
throw new TypeError("'text' must be a string");
}
return text.replace(SNODE_PATTERN, REDACTION_PLACEHOLDER);
};
// redactGroupIds :: String -> String
exports.redactGroupIds = text => {
if (!is.string(text)) {
@ -84,7 +93,8 @@ exports.redactSensitivePaths = exports._redactPath(APP_ROOT_PATH);
exports.redactAll = compose(
exports.redactSensitivePaths,
exports.redactGroupIds,
exports.redactSessionID
exports.redactSessionID,
exports.redactSnodeIP
);
const removeNewlines = text => text.replace(/\r?\n|\r/g, '');

View File

@ -214,7 +214,6 @@ exports.deleteData = deleteOnDisk => {
exports.isVoiceMessage = AttachmentTS.isVoiceMessage;
exports.save = AttachmentTS.save;
exports.getFileExtension = AttachmentTS.getFileExtension;
exports.getSuggestedFilenameSending = AttachmentTS.getSuggestedFilenameSending;
exports.arrayBufferFromFile = AttachmentTS.arrayBufferFromFile;
const THUMBNAIL_SIZE = 150;

10
main.js
View File

@ -492,20 +492,16 @@ setTimeout(readyForUpdates, TEN_MINUTES);
function openReleaseNotes() {
shell.openExternal(
`https://github.com/loki-project/session-desktop/releases/tag/v${app.getVersion()}`
`https://github.com/oxen-io/session-desktop/releases/tag/v${app.getVersion()}`
);
}
function openNewBugForm() {
shell.openExternal(
'https://github.com/loki-project/session-desktop/issues/new'
);
shell.openExternal('https://github.com/oxen-io/session-desktop/issues/new');
}
function openSupportPage() {
shell.openExternal(
'https://docs.loki.network/LokiServices/Messenger/Session/'
);
shell.openExternal('https://docs.oxen.io/products-built-on-oxen/session');
}
function setupWithImport() {

View File

@ -305,6 +305,7 @@
"debug_log_preload.js",
"password_preload.js",
"main.js",
"certificates/**",
"images/**",
"fonts/*",
"build/assets",

View File

@ -386,21 +386,25 @@ window.seedNodeList = JSON.parse(config.seedNodeList);
const { OnionPaths } = require('./ts/session/onions');
const { locale } = config;
window.i18n = i18n.setup(locale, localeMessages);
// moment does not support es-419 correctly (and cause white screen on app start)
const localeForMoment = locale === 'es-419' ? 'es' : locale;
const { locale: localFromEnv } = config;
window.i18n = i18n.setup(localFromEnv, localeMessages);
// moment does not support es-419 correctly (and cause white screen on app start)
window.moment = require('moment');
window.moment.updateLocale(localeForMoment, {
// Default to the locale from env. It will be overriden if moment
// does not recognize it with what moment knows which is the closest.
// i.e. es-419 will return 'es'.
// We just need to use what we got from moment on the updateLocale below
const localeSetForMoment = window.moment.locale(localFromEnv);
window.moment.updateLocale(localeSetForMoment, {
relativeTime: {
s: window.i18n('timestamp_s'),
m: window.i18n('timestamp_m'),
h: window.i18n('timestamp_h'),
},
});
window.moment.locale(localeForMoment);
window.OnionPaths = OnionPaths;

View File

@ -42,6 +42,7 @@ type PropsHousekeeping = {
onUnblockContact?: () => void;
onInviteContacts?: () => void;
onClearNickname?: () => void;
onMarkAllRead: () => void;
theme: DefaultTheme;
};
@ -62,7 +63,6 @@ class ConversationListItem extends React.PureComponent<Props> {
public renderAvatar() {
const {
avatarPath,
i18n,
name,
phoneNumber,
profileName,

View File

@ -83,6 +83,8 @@ interface Props {
onAvatarClick?: (userPubKey: string) => void;
onUpdateGroupName: () => void;
onMarkAllRead: () => void;
memberAvatars?: Array<ConversationAvatar>; // this is added by usingClosedConversationDetails
theme: DefaultTheme;
}

View File

@ -102,11 +102,14 @@ const Section = (props: { type: SectionType; avatarPath?: string }) => {
iconType = SessionIconType.Moon;
}
const unreadToShow =
type === SectionType.Message ? unreadMessageCount : undefined;
return (
<SessionIconButton
iconSize={SessionIconSize.Medium}
iconType={iconType}
notificationCount={unreadMessageCount}
notificationCount={unreadToShow}
onClick={handleClick}
isSelected={isSelected}
theme={theme}

View File

@ -127,8 +127,7 @@ export class LeftPaneContactSection extends React.Component<Props, State> {
if (error) {
ToastUtils.pushToastError('addContact', error);
} else {
// tslint:disable-next-line: no-floating-promises
ConversationController.getInstance()
void ConversationController.getInstance()
.getOrCreateAndWait(sessionID, 'private')
.then(() => {
this.props.openConversationExternal(sessionID);

View File

@ -434,6 +434,10 @@ export class SessionConversation extends React.Component<Props, State> {
window.Whisper.events.trigger('inviteContacts', conversation);
},
onMarkAllRead: () => {
void conversation.markReadBouncy(Date.now());
},
onAddModerators: () => {
window.Whisper.events.trigger('addModerators', conversation);
},

View File

@ -2,7 +2,7 @@ import React from 'react';
import { arrayBufferFromFile } from '../../../types/Attachment';
import { AttachmentUtil, LinkPreviewUtil } from '../../../util';
import { StagedLinkPreviewData } from './SessionCompositionBox';
import fetch from 'node-fetch';
import { default as insecureNodeFetch } from 'node-fetch';
import { fetchLinkPreviewImage } from '../../../util/linkPreviewFetch';
import { AbortSignal } from 'abort-controller';
import { StagedLinkPreview } from '../../conversation/StagedLinkPreview';
@ -37,8 +37,10 @@ export const getPreview = async (
throw new Error('Link not safe for preview');
}
window.log.info('insecureNodeFetch => plaintext for getPreview()');
const linkPreviewMetadata = await LinkPreviewUtil.fetchLinkPreviewMetadata(
fetch,
insecureNodeFetch,
url,
abortSignal
);
@ -51,8 +53,10 @@ export const getPreview = async (
if (imageHref && window.Signal.LinkPreviews.isLinkSafeToPreview(imageHref)) {
let objectUrl: void | string;
try {
window.log.info('insecureNodeFetch => plaintext for getPreview()');
const fullSizeImage = await fetchLinkPreviewImage(
fetch,
insecureNodeFetch,
imageHref,
abortSignal
);

View File

@ -9,6 +9,7 @@ import {
getDisappearingMenuItem,
getInviteContactMenuItem,
getLeaveGroupMenuItem,
getMarkAllReadMenuItem,
getRemoveModeratorsMenuItem,
getUpdateGroupNameMenuItem,
} from './Menu';
@ -31,6 +32,7 @@ export type PropsConversationHeaderMenu = {
onInviteContacts?: () => void;
onLeaveGroup: () => void;
onMarkAllRead: () => void;
onAddModerators: () => void;
onRemoveModerators: () => void;
onUpdateGroupName: () => void;
@ -55,6 +57,7 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
onDeleteMessages,
onDeleteContact,
onCopyPublicKey,
onMarkAllRead,
onLeaveGroup,
onAddModerators,
onRemoveModerators,
@ -86,6 +89,7 @@ export const ConversationHeaderMenu = (props: PropsConversationHeaderMenu) => {
)}
{getCopyMenuItem(isPublic, isGroup, onCopyPublicKey, window.i18n)}
{getMarkAllReadMenuItem(onMarkAllRead, window.i18n)}
{getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n)}
{getAddModeratorsMenuItem(
isAdmin,

View File

@ -9,6 +9,7 @@ import {
getDeleteMessagesMenuItem,
getInviteContactMenuItem,
getLeaveGroupMenuItem,
getMarkAllReadMenuItem,
} from './Menu';
export type PropsContextConversationItem = {
@ -25,6 +26,7 @@ export type PropsContextConversationItem = {
onDeleteContact?: () => void;
onLeaveGroup?: () => void;
onBlockContact?: () => void;
onMarkAllRead: () => void;
onCopyPublicKey?: () => void;
onUnblockContact?: () => void;
onInviteContacts?: () => void;
@ -48,6 +50,7 @@ export const ConversationListItemContextMenu = (
onBlockContact,
onClearNickname,
onCopyPublicKey,
onMarkAllRead,
onUnblockContact,
onInviteContacts,
onLeaveGroup,
@ -81,6 +84,8 @@ export const ConversationListItemContextMenu = (
onCopyPublicKey,
window.i18n
)}
{getMarkAllReadMenuItem(onMarkAllRead, window.i18n)}
{getDeleteMessagesMenuItem(isPublic, onDeleteMessages, window.i18n)}
{getInviteContactMenuItem(
type === 'group',

View File

@ -32,8 +32,9 @@ function showDeleteMessages(isPublic: boolean): boolean {
return !isPublic;
}
// we want to show the copyId for open groups and private chats only
function showCopyId(isPublic: boolean, isGroup: boolean): boolean {
return !isGroup; // || isPublic;
return !isGroup || isPublic;
}
function showDeleteContact(
@ -196,12 +197,21 @@ export function getCopyMenuItem(
i18n: LocalizerType
): JSX.Element | null {
if (showCopyId(Boolean(isPublic), Boolean(isGroup))) {
const copyIdLabel = i18n('copySessionID');
const copyIdLabel = isPublic
? i18n('copyOpenGroupURL')
: i18n('copySessionID');
return <Item onClick={action}>{copyIdLabel}</Item>;
}
return null;
}
export function getMarkAllReadMenuItem(
action: any,
i18n: LocalizerType
): JSX.Element | null {
return <Item onClick={action}>{i18n('markAllAsRead')}</Item>;
}
export function getDisappearingMenuItem(
isPublic: boolean | undefined,
isKickedFromGroup: boolean | undefined,

View File

@ -423,6 +423,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
onInviteContacts: () => {
window.Whisper.events.trigger('inviteContacts', this);
},
onMarkAllRead: () => {
void this.markReadBouncy(Date.now());
},
onClearNickname: () => {
void this.setLokiProfile({ displayName: null });
},
@ -434,9 +437,9 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
const newAdmins = _.sortBy(groupAdmins);
if (_.isEqual(existingAdmins, newAdmins)) {
window.log.info(
'Skipping updates of groupAdmins/moderators. No change detected.'
);
// window.log.info(
// 'Skipping updates of groupAdmins/moderators. No change detected.'
// );
return;
}
this.set({ groupAdmins });
@ -468,7 +471,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
public async getUnreadCount() {
window.log.warn('getUnreadCount is slow');
const unreadCount = await getUnreadCountByConversation(this.id);
return unreadCount;
@ -1351,6 +1353,14 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
}
public copyPublicKey() {
if (this.isPublic()) {
const atIndex = this.id.indexOf('@') as number;
const openGroupUrl = this.id.substr(atIndex + 1);
window.clipboard.writeText(openGroupUrl);
ToastUtils.pushCopiedToClipBoard();
return;
}
window.clipboard.writeText(this.id);
ToastUtils.pushCopiedToClipBoard();

View File

@ -2,8 +2,9 @@ import { allowOnlyOneAtATime } from '../../../js/modules/loki_primitives';
import { getGuardNodes } from '../../../ts/data/data';
import * as SnodePool from '../snode_api/snodePool';
import _ from 'lodash';
import fetch from 'node-fetch';
import { default as insecureNodeFetch } from 'node-fetch';
import { UserUtils } from '../utils';
import { snodeHttpsAgent } from '../snode_api/onions';
type Snode = SnodePool.Snode;
@ -155,23 +156,22 @@ export class OnionPaths {
body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' },
timeout: 10000, // 10s, we want a smaller timeout for testing
agent: snodeHttpsAgent,
};
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
let response;
try {
// Log this line for testing
// curl -k -X POST -H 'Content-Type: application/json' -d '"+fetchOptions.body.replace(/"/g, "\\'")+"'", url
response = await fetch(url, fetchOptions);
window.log.info('insecureNodeFetch => plaintext for testGuardNode');
response = await insecureNodeFetch(url, fetchOptions);
} catch (e) {
if (e.type === 'request-timeout') {
log.warn('test timeout for node,', snode);
}
return false;
} finally {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
}
if (!response.ok) {
@ -312,6 +312,6 @@ export class OnionPaths {
this.onionPaths.push({ path, bad: false });
}
log.info(`Built ${this.onionPaths.length} onion paths`, this.onionPaths);
log.info(`Built ${this.onionPaths.length} onion paths`);
}
}

View File

@ -1,48 +1,15 @@
import fetch from 'node-fetch';
import https from 'https';
import { default as insecureNodeFetch } from 'node-fetch';
import { Snode } from './snodePool';
import { lokiOnionFetch, SnodeResponse } from './onions';
const snodeHttpsAgent = new https.Agent({
rejectUnauthorized: false,
});
async function lokiPlainFetch(
url: string,
fetchOptions: any
): Promise<boolean | SnodeResponse> {
const { log } = window;
if (url.match(/https:\/\//)) {
// import that this does not get set in lokiFetch fetchOptions
fetchOptions.agent = snodeHttpsAgent;
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
} else {
log.debug('lokirpc:::lokiFetch - http communication', url);
}
const response = await fetch(url, fetchOptions);
// restore TLS checking
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
if (!response.ok) {
throw new window.textsecure.HTTPError('Loki_rpc error', response);
}
const result = await response.text();
return {
body: result,
status: response.status,
};
}
import { lokiOnionFetch, snodeHttpsAgent, SnodeResponse } from './onions';
interface FetchOptions {
method: string;
}
// A small wrapper around node-fetch which deserializes response
// returns nodeFetch response or false
// returns insecureNodeFetch response or false
async function lokiFetch(
url: string,
options: FetchOptions,
@ -64,7 +31,23 @@ async function lokiFetch(
return await lokiOnionFetch(fetchOptions.body, targetNode);
}
return await lokiPlainFetch(url, fetchOptions);
if (url.match(/https:\/\//)) {
// import that this does not get set in lokiFetch fetchOptions
fetchOptions.agent = snodeHttpsAgent;
}
window.log.warn(`insecureNodeFetch => lokiFetch of ${url}`);
const response = await insecureNodeFetch(url, fetchOptions);
if (!response.ok) {
throw new window.textsecure.HTTPError('Loki_rpc error', response);
}
const result = await response.text();
return {
body: result,
status: response.status,
};
} catch (e) {
if (e.code === 'ENOTFOUND') {
throw new window.textsecure.NotFoundError('Failed to resolve address', e);

View File

@ -1,4 +1,4 @@
import fetch from 'node-fetch';
import { default as insecureNodeFetch } from 'node-fetch';
import https from 'https';
import { Snode } from './snodePool';
@ -353,7 +353,7 @@ const processOnionResponse = async (
}
};
const snodeHttpsAgent = new https.Agent({
export const snodeHttpsAgent = new https.Agent({
rejectUnauthorized: false,
});
@ -457,7 +457,10 @@ const sendOnionRequest = async (
const target = useV2 ? '/onion_req/v2' : '/onion_req';
const guardUrl = `https://${nodePath[0].ip}:${nodePath[0].port}${target}`;
const response = await fetch(guardUrl, guardFetchOptions);
// no logs for that one as we do need to call insecureNodeFetch to our guardNode
// window.log.info('insecureNodeFetch => plaintext for sendOnionRequest');
const response = await insecureNodeFetch(guardUrl, guardFetchOptions);
return processOnionResponse(
reqIdx,

View File

@ -1,10 +1,21 @@
// we don't throw or catch here
import { default as insecureNodeFetch } from 'node-fetch';
import https from 'https';
import fetch from 'node-fetch';
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import tls from 'tls';
import Electron from 'electron';
const { remote } = Electron;
import { snodeRpc } from './lokiRpc';
import { sendOnionRequestLsrpcDest, SnodeResponse } from './onions';
import {
sendOnionRequestLsrpcDest,
snodeHttpsAgent,
SnodeResponse,
} from './onions';
import { sleepFor } from '../../../js/modules/loki_primitives';
@ -17,10 +28,10 @@ import {
updateSnodesFor,
} from './snodePool';
const snodeHttpsAgent = new https.Agent({
rejectUnauthorized: false,
});
/**
* Currently unused. If we need it again, be sure to update it to onion routing rather
* than using a plain nodeFetch
*/
export async function getVersion(
node: Snode,
retries: number = 0
@ -30,11 +41,13 @@ export async function getVersion(
const { log } = window;
try {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
const result = await fetch(`https://${node.ip}:${node.port}/get_stats/v1`, {
agent: snodeHttpsAgent,
});
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1';
window.log.warn('insecureNodeFetch => plaintext for getVersion');
const result = await insecureNodeFetch(
`https://${node.ip}:${node.port}/get_stats/v1`,
{
agent: snodeHttpsAgent,
}
);
const data = await result.json();
if (data.version) {
return data.version;
@ -74,6 +87,93 @@ export async function getVersion(
}
}
const sha256 = (s: string) => {
return crypto
.createHash('sha256')
.update(s)
.digest('base64');
};
const getSslAgentForSeedNode = (seedNodeHost: string) => {
let filePrefix = '';
let pubkey256 = '';
let cert256 = '';
switch (seedNodeHost) {
case 'storage.seed1.loki.network':
filePrefix = 'storage-seed-1';
pubkey256 = 'JOsnIcAanVbgECNA8lHtC8f/cqN9m8EP7jKT6XCjeL8=';
cert256 =
'6E:2B:AC:F3:6E:C1:FF:FF:24:F3:CA:92:C6:94:81:B4:82:43:DF:C7:C6:03:98:B8:F5:6B:7D:30:7B:16:C1:CB';
break;
case 'storage.seed3.loki.network':
filePrefix = 'storage-seed-3';
pubkey256 = 'mMmZD3lG4Fi7nTC/EWzRVaU3bbCLsH6Ds2FHSTpo0Rk=';
cert256 =
'24:13:4C:0A:03:D8:42:A6:09:DE:35:76:F4:BD:FB:11:60:DB:F9:88:9F:98:46:B7:60:A6:60:0C:4C:CF:60:72';
break;
case 'public.loki.foundation':
filePrefix = 'public-loki-foundation';
pubkey256 = 'W+Zv52qlcm1BbdpJzFwxZrE7kfmEboq7h3Dp/+Q3RPg=';
cert256 =
'40:E4:67:7D:18:6B:4D:08:8D:E9:D5:47:52:25:B8:28:E0:D3:63:99:9B:38:46:7D:92:19:5B:61:B9:AE:0E:EA';
break;
default:
throw new Error(`Unknown seed node: ${seedNodeHost}`);
}
// tslint:disable: non-literal-fs-path
// read the cert each time. We only run this request once for each seed node nevertheless.
const appPath = remote.app.getAppPath();
const crt = fs.readFileSync(
path.join(appPath, `/certificates/${filePrefix}.crt`),
'utf-8'
);
// debugger;
const sslOptions = {
// as the seed nodes are using a self signed certificate, we have to provide it here.
ca: crt,
// we might need to selectively disable that for tests on swarm-testing or so.
// we have to reject them, otherwise our errors returned in the checkServerIdentity are simply not making the call fail.
// so in production, rejectUnauthorized must be true.
rejectUnauthorized: true,
keepAlive: false,
checkServerIdentity: (host: string, cert: any) => {
// Make sure the certificate is issued to the host we are connected to
const err = tls.checkServerIdentity(host, cert);
if (err) {
return err;
}
// we might need to selectively disable that for tests on swarm-testing or so.
// Pin the public key, similar to HPKP pin-sha25 pinning
if (sha256(cert.pubkey) !== pubkey256) {
const msg =
'Certificate verification error: ' +
`The public key of '${cert.subject.CN}' ` +
'does not match our pinned fingerprint';
return new Error(msg);
}
// Pin the exact certificate, rather than the pub key
if (cert.fingerprint256 !== cert256) {
const msg =
'Certificate verification error: ' +
`The certificate of '${cert.subject.CN}' ` +
'does not match our pinned fingerprint';
return new Error(msg);
}
return undefined;
},
};
// we're creating a new Agent that will now use the certs we have configured
return new https.Agent(sslOptions);
};
export async function getSnodesFromSeedUrl(urlObj: URL): Promise<Array<any>> {
const { log } = window;
@ -99,14 +199,18 @@ export async function getSnodesFromSeedUrl(urlObj: URL): Promise<Array<any>> {
method: 'get_n_service_nodes',
params,
};
const sslAgent = getSslAgentForSeedNode(urlObj.hostname);
const fetchOptions = {
method: 'POST',
timeout: 10000,
body: JSON.stringify(body),
};
const response = await fetch(url, fetchOptions);
agent: sslAgent,
};
window.log.info('insecureNodeFetch => plaintext for getSnodesFromSeedUrl');
const response = await insecureNodeFetch(url, fetchOptions);
if (response.status !== 200) {
log.error(

View File

@ -1,22 +1,18 @@
import semver from 'semver';
import _ from 'lodash';
import {
abortableIterator,
allowOnlyOneAtATime,
} from '../../../js/modules/loki_primitives';
import {
getSnodesFromSeedUrl,
getVersion,
requestSnodesForPubkey,
} from './serviceNodeAPI';
import { getSnodesFromSeedUrl, requestSnodesForPubkey } from './serviceNodeAPI';
import {
getSwarmNodesForPubkey,
updateSwarmNodesForPubkey,
} from '../../../ts/data/data';
import semver from 'semver';
import _ from 'lodash';
export type SnodeEdKey = string;
const MIN_NODES = 3;
@ -60,6 +56,7 @@ async function tryGetSnodeListFromLokidSeednode(
let snodes = [];
try {
const tryUrl = new URL(seedNode.url);
snodes = await getSnodesFromSeedUrl(tryUrl);
// throw before clearing the lock, so the retries can kick in
if (snodes.length === 0) {
@ -108,8 +105,7 @@ export function markNodeUnreachable(snode: Snode): void {
for (const [pubkey, nodes] of nodesForPubkey) {
const edkeys = _.filter(nodes, edkey => edkey !== snode.pubkey_ed25519);
// tslint:disable-next-line no-floating-promises
internalUpdateSnodesFor(pubkey, edkeys);
void internalUpdateSnodesFor(pubkey, edkeys);
}
log.warn(
@ -137,13 +133,18 @@ function compareSnodes(lhs: any, rhs: any): boolean {
return lhs.pubkey_ed25519 === rhs.pubkey_ed25519;
}
// WARNING: this leaks our IP to all snodes but with no other identifying information
// except "that a client started up" or "ran out of random pool snodes"
// and the order of the list is randomized, so a snode can't tell if it just started or not
/**
* Request the version of the snode.
* THIS IS AN INSECURE NODE FETCH and leaks our IP to all snodes but with no other identifying information
* except "that a client started up" or "ran out of random pool snodes"
* and the order of the list is randomized, so a snode can't tell if it just started or not
*/
async function requestVersion(node: any): Promise<void> {
const { log } = window;
const result = await getVersion(node);
// WARNING: getVersion is doing an insecure node fetch.
// be sure to update getVersion to onion routing if we need this call again.
const result = false; // await getVersion(node);
if (result === false) {
return;
@ -178,9 +179,14 @@ export function getNodesMinVersion(minVersion: string): Array<Snode> {
);
}
// now get version for all snodes
// also acts an early online test/purge of bad nodes
export async function getAllVerionsForRandomSnodePool(): Promise<void> {
/**
* Currently unused as it makes call over insecure node fetch and we don't need
* to filter out nodes by versions anymore.
*
* now get version for all snodes
* also acts an early online test/purge of bad nodes
*/
export async function getAllVersionsForRandomSnodePool(): Promise<void> {
const { log } = window;
// let count = 0;
@ -192,7 +198,7 @@ export async function getAllVerionsForRandomSnodePool(): Promise<void> {
await requestVersion(node);
} catch (e) {
log.error(
'LokiSnodeAPI::_getAllVerionsForRandomSnodePool - error',
'LokiSnodeAPI::_getAllVersionsForRandomSnodePool - error',
e.code,
e.message
);
@ -211,7 +217,7 @@ export async function getAllVerionsForRandomSnodePool(): Promise<void> {
return curVal;
}, []);
log.debug(
`LokiSnodeAPI::_getAllVerionsForRandomSnodePool - ${versions.length} versions retrieved from network!:`,
`LokiSnodeAPI::_getAllVersionsForRandomSnodePool - ${versions.length} versions retrieved from network!:`,
versions.join(',')
);
}
@ -248,8 +254,7 @@ async function getSnodeListFromLokidSeednode(
'seed nodes total',
seedNodes.length
);
// tslint:disable-next-line:no-floating-promises
getSnodeListFromLokidSeednode(seedNodes, retries + 1);
void getSnodeListFromLokidSeednode(seedNodes, retries + 1);
}, retries * retries * 5000);
} else {
log.error('loki_snode_api::getSnodeListFromLokidSeednode - failing');
@ -262,7 +267,7 @@ async function getSnodeListFromLokidSeednode(
async function refreshRandomPoolDetail(seedNodes: Array<any>): Promise<void> {
const { log } = window;
// are we running any _getAllVerionsForRandomSnodePool
// are we running any _getAllVersionsForRandomSnodePool
if (stopGetAllVersionPromiseControl !== false) {
// we are, stop them
stopGetAllVersionPromiseControl();
@ -286,8 +291,9 @@ async function refreshRandomPoolDetail(seedNodes: Array<any>): Promise<void> {
randomSnodePool.length,
'snodes'
);
// tslint:disable-next-line:no-floating-promises
getAllVerionsForRandomSnodePool();
// Warning: the call below will call getVersions to all existing nodes.
// And not with onion routing
// void getAllVersionsForRandomSnodePool();
} catch (e) {
log.warn('LokiSnodeAPI::refreshRandomPool - error', e.code, e.message);
/*
@ -358,8 +364,7 @@ export async function getSnodesFor(pubkey: string): Promise<Array<Snode>> {
const freshNodes = _.shuffle(await requestSnodesForPubkey(pubkey));
const edkeys = freshNodes.map((n: Snode) => n.pubkey_ed25519);
// tslint:disable-next-line no-floating-promises
internalUpdateSnodesFor(pubkey, edkeys);
void internalUpdateSnodesFor(pubkey, edkeys);
// TODO: We could probably check that the retuned sndoes are not "unreachable"
return freshNodes;

View File

@ -29,7 +29,6 @@ export function processMessage(message: string, options: any = {}) {
const dataPlaintext = new Uint8Array(StringUtils.encode(message, 'base64'));
const messageBuf = SignalService.WebSocketMessage.decode(dataPlaintext);
if (messageBuf.type === SignalService.WebSocketMessage.Type.REQUEST) {
// tslint:disable-next-line no-floating-promises
Receiver.handleRequest(messageBuf.request?.body, options);
}
} catch (error) {

View File

@ -1,5 +1,5 @@
import { processMessage, SwarmPolling } from './swarmPolling';
import fetch from 'node-fetch';
import { default as insecureNodeFetch } from 'node-fetch';
import { PubKey } from '../types';
export class SwarmPollingStub extends SwarmPolling {
@ -12,7 +12,8 @@ export class SwarmPollingStub extends SwarmPolling {
method: 'GET',
};
const res = await fetch(
// insecureNodeFetch but this is a stub
const res = await insecureNodeFetch(
`${this.baseUrl}/messages?pubkey=${pubkeyStr}`,
get
);

View File

@ -87,15 +87,16 @@ export interface ConversationType {
groupAdmins?: Array<string>; // admins for closed groups and moderators for open groups
members?: Array<string>; // members for closed groups only
onClick?: () => any;
onBlockContact?: () => any;
onUnblockContact?: () => any;
onCopyPublicKey?: () => any;
onDeleteContact?: () => any;
onLeaveGroup?: () => any;
onDeleteMessages?: () => any;
onInviteContacts?: () => any;
onClearNickname?: () => any;
onClick?: () => void;
onBlockContact?: () => void;
onUnblockContact?: () => void;
onCopyPublicKey?: () => void;
onDeleteContact?: () => void;
onLeaveGroup?: () => void;
onDeleteMessages?: () => void;
onInviteContacts?: () => void;
onMarkAllRead?: () => void;
onClearNickname?: () => void;
}
export type ConversationLookupType = {

View File

@ -1,6 +1,6 @@
import { StringUtils } from '../../../../session/utils';
import fetch from 'node-fetch';
import { default as insecureNodeFetch } from 'node-fetch';
class StubMessageAPI {
public ourKey: string;
@ -23,7 +23,9 @@ class StubMessageAPI {
};
const data64 = StringUtils.decode(data, 'base64');
await fetch(
// insecureNodeFetch but this is a stub
await insecureNodeFetch(
`${
this.baseUrl
}/messages?pubkey=${pubKey}&timestamp=${messageTimeStamp}&data=${encodeURIComponent(

View File

@ -54,6 +54,46 @@ describe('Attachment', () => {
const expected = 'session-attachment_003.mov';
assert.strictEqual(actual, expected);
});
it('should generate a filename with an extension if contentType is not setup', () => {
const attachment: Attachment.AttachmentType = {
fileName: 'funny-cat.ini',
url: 'funny-cat.ini',
contentType: '',
};
const actual = Attachment.getSuggestedFilename({
attachment,
index: 3,
});
const expected = 'session-attachment_003.ini';
assert.strictEqual(actual, expected);
});
it('should generate a filename with an extension if contentType is text/plain', () => {
const attachment: Attachment.AttachmentType = {
fileName: 'funny-cat.txt',
url: 'funny-cat.txt',
contentType: 'text/plain',
};
const actual = Attachment.getSuggestedFilename({
attachment,
index: 3,
});
const expected = 'session-attachment_003.txt';
assert.strictEqual(actual, expected);
});
it('should generate a filename with an extension if contentType is json', () => {
const attachment: Attachment.AttachmentType = {
fileName: 'funny-cat.json',
url: 'funny-cat.json',
contentType: '',
};
const actual = Attachment.getSuggestedFilename({
attachment,
index: 3,
});
const expected = 'session-attachment_003.json';
assert.strictEqual(actual, expected);
});
});
context('for attachment without filename', () => {
it('should generate a filename based on timestamp', () => {

View File

@ -396,8 +396,17 @@ export const getSuggestedFilenameSending = ({
export const getFileExtension = (
attachment: AttachmentType
): string | undefined => {
if (!attachment.contentType) {
return;
// we override textplain to the extension of the file
if (!attachment.contentType || attachment.contentType === 'text/plain') {
if (attachment.fileName?.length) {
const dotLastIndex = attachment.fileName.lastIndexOf('.');
if (dotLastIndex !== -1) {
return attachment.fileName.substring(dotLastIndex + 1);
} else {
return undefined;
}
}
return undefined;
}
switch (attachment.contentType) {

View File

@ -9,7 +9,6 @@ import {
IMAGE_WEBP,
MIMEType,
} from '../types/MIME';
import { PromiseUtils } from '../session/utils';
const MAX_REQUEST_COUNT_WITH_REDIRECTS = 20;
// tslint:disable: prefer-for-of

View File

@ -4239,14 +4239,6 @@
"reasonCategory": "falseMatch",
"updated": "2018-09-19T18:13:29.628Z"
},
{
"rule": "jQuery-append(",
"path": "node_modules/node-fetch/lib/headers.js",
"line": "\t\t\t\tself.append(prop, item.toString());",
"lineNumber": 40,
"reasonCategory": "falseMatch",
"updated": "2018-09-19T18:13:29.628Z"
},
{
"rule": "jQuery-$(",
"path": "node_modules/node-forge/dist/forge.all.min.js",