Merge remote-tracking branch 'upstream/clearnet' into scoring-system

This commit is contained in:
Audric Ackermann 2021-05-26 14:15:54 +10:00
commit a2ee3ac98f
No known key found for this signature in database
GPG key ID: 999F434D76324AD4
16 changed files with 215 additions and 62 deletions

View file

@ -14,6 +14,39 @@ Please search for any [existing issues](https://github.com/oxen-io/session-deskt
Build instructions can be found in [BUILDING.md](BUILDING.md).
## Verifing signatures
Get Kee's key and import it:
```
wget https://raw.githubusercontent.com/oxen-io/oxen-core/master/utils/gpg_keys/KeeJef.asc
gpg --import KeeJef.asc
```
Get the signed hash for this release, the SESSION_VERSION needs to be updated for the release you want to verify
```
export SESSION_VERSION=1.6.1
wget https://github.com/oxen-io/session-desktop/releases/download/v$SESSION_VERSION/signatures.asc
```
Verify the signature of the hashes of the files
```
gpg --verify signatures.asc 2>&1 |grep "Good signature from"
```
The command above should print "`Good signature from "Kee Jefferys...`"
If it does, the hashes are valid but we still have to make the sure the signed hashes matches the downloaded files.
Make sure the two commands below returns the same hash.
If they do, files are valid
```
sha256sum session-desktop-linux-amd64-$SESSION_VERSION.deb
grep .deb signatures.asc
```
## Debian repository
Please visit https://deb.oxen.io/<br/>

View file

@ -10,7 +10,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Security-Policy" content="default-src 'none';
child-src 'self';
connect-src 'self' https: wss:;
connect-src 'self' https: wss: blob:;
font-src 'self';
form-action 'self';
frame-src 'none';
@ -167,4 +167,4 @@
<script type='text/javascript' src='js/background.js'></script>
</body>
</html>
</html>

View file

@ -425,7 +425,7 @@
avatarPath,
onOk: async (newName, avatar) => {
let newAvatarPath = '';
let url = null;
let fileUrl = null;
let profileKey = null;
if (avatar) {
const data = await readFile({ file: avatar });
@ -463,22 +463,18 @@
profileKey
);
const avatarPointer = await libsession.Utils.AttachmentUtils.uploadAvatarV1({
...dataResized,
data: encryptedData,
size: encryptedData.byteLength,
});
const avatarPointer = await window.Fsv2.uploadFileToFsV2(encryptedData);
({ url } = avatarPointer);
({ fileUrl } = avatarPointer);
storage.put('profileKey', profileKey);
conversation.set('avatarPointer', url);
conversation.set('avatarPointer', fileUrl);
const upgraded = await Signal.Migrations.processNewAttachment({
isRaw: true,
data: data.data,
url,
url: fileUrl,
});
newAvatarPath = upgraded.path;
// Replace our temporary image with the attachment pointer from the server:
@ -513,14 +509,6 @@
// so we could disable this here
// or least it enable for the quickest response
window.lokiPublicChatAPI.setProfileName(newName);
if (avatar) {
window
.getConversationController()
.getConversations()
.filter(convo => convo.isPublic())
.forEach(convo => convo.trigger('ourAvatarChanged', { url, profileKey }));
}
},
});
}

View file

@ -13,4 +13,5 @@ export interface LibTextsecureCryptoInterface {
theirDigest: ArrayBuffer
): Promise<ArrayBuffer>;
decryptProfile(data: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer>;
encryptProfile(data: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer>;
}

View file

@ -2,7 +2,7 @@
"name": "session-desktop",
"productName": "Session",
"description": "Private messaging from your desktop",
"version": "1.6.2",
"version": "1.6.4",
"license": "GPL-3.0",
"author": {
"name": "Loki Project",

View file

@ -398,6 +398,7 @@ window.addEventListener('contextmenu', e => {
});
window.NewReceiver = require('./ts/receiver/receiver');
window.Fsv2 = require('./ts/fileserver/FileServerApiV2');
window.DataMessageReceiver = require('./ts/receiver/dataMessage');
window.NewSnodeAPI = require('./ts/session/snode_api/SNodeAPI');
window.SnodePool = require('./ts/session/snode_api/snodePool');

View file

@ -91,7 +91,11 @@ export const Avatar = (props: Props) => {
// contentType is not important
const { urlToLoad } = useEncryptedFileFetch(avatarPath || '', '');
const handleImageError = () => {
window?.log?.warn('Avatar: Image failed to load; failing over to placeholder', urlToLoad);
window.log.warn(
'Avatar: Image failed to load; failing over to placeholder',
urlToLoad,
avatarPath
);
setImageBroken(true);
};

View file

@ -229,7 +229,7 @@ class ConversationListItem extends React.PureComponent<Props> {
const displayName = isMe ? i18n('noteToSelf') : profileName;
let shouldShowPubkey = false;
if (!name || name.length === 0) {
if ((!name || name.length === 0) && (!displayName || displayName.length === 0)) {
shouldShowPubkey = true;
}

View file

@ -7,11 +7,13 @@ import { ConversationController } from '../../session/conversations';
import { UserUtils } from '../../session/utils';
import { syncConfigurationIfNeeded } from '../../session/utils/syncUtils';
import { DAYS, MINUTES } from '../../session/utils/Number';
import {
createOrUpdateItem,
generateAttachmentKeyIfEmpty,
getItemById,
hasSyncedInitialConfigurationItem,
removeItemById,
lastAvatarUploadTimestamp,
} from '../../data/data';
import { OnionPaths } from '../../session/onions';
import { getMessageQueue } from '../../session/sending';
@ -29,11 +31,18 @@ import { useInterval } from '../../hooks/useInterval';
import { clearSearch } from '../../state/ducks/search';
import { showLeftPaneSection } from '../../state/ducks/section';
import { cleanUpOldDecryptedMedias } from '../../session/crypto/DecryptedAttachmentsManager';
import {
cleanUpOldDecryptedMedias,
getDecryptedMediaUrl,
} from '../../session/crypto/DecryptedAttachmentsManager';
import { OpenGroupManagerV2 } from '../../opengroup/opengroupV2/OpenGroupManagerV2';
import { loadDefaultRooms } from '../../opengroup/opengroupV2/ApiUtil';
import { forceRefreshRandomSnodePool } from '../../session/snode_api/snodePool';
import { SwarmPolling } from '../../session/snode_api/swarmPolling';
import { IMAGE_JPEG } from '../../types/MIME';
import { FSv2 } from '../../fileserver';
import { stringToArrayBuffer } from '../../session/utils/String';
import { debounce } from 'underscore';
// tslint:disable-next-line: no-import-side-effect no-submodule-imports
export enum SectionType {
@ -45,6 +54,20 @@ export enum SectionType {
Moon,
}
const showUnstableAttachmentsDialogIfNeeded = async () => {
const alreadyShown = (await getItemById('showUnstableAttachmentsDialog'))?.value;
if (!alreadyShown) {
window.confirmationDialog({
title: 'File server update',
message:
"We're upgrading the way files are stored. File transfer may be unstable for the next 24-48 hours.",
});
await createOrUpdateItem({ id: 'showUnstableAttachmentsDialog', value: true });
}
};
const Section = (props: { type: SectionType; avatarPath?: string }) => {
const ourNumber = useSelector(getOurNumber);
const unreadMessageCount = useSelector(getUnreadMessageCount);
@ -149,6 +172,81 @@ const triggerSyncIfNeeded = async () => {
}
};
const triggerAvatarReUploadIfNeeded = async () => {
const lastTimeStampAvatarUpload = (await getItemById(lastAvatarUploadTimestamp))?.value || 0;
if (Date.now() - lastTimeStampAvatarUpload > DAYS * 14) {
window.log.info('Reuploading avatar...');
// reupload the avatar
const ourConvo = ConversationController.getInstance().get(UserUtils.getOurPubKeyStrFromCache());
if (!ourConvo) {
window.log.warn('ourConvo not found... This is not a valid case');
return;
}
const profileKey = window.textsecure.storage.get('profileKey');
if (!profileKey) {
window.log.warn('our profileKey not found... This is not a valid case');
return;
}
const currentAttachmentPath = ourConvo.getAvatarPath();
if (!currentAttachmentPath) {
window.log.warn('No attachment currently set for our convo.. Nothing to do.');
return;
}
const decryptedAvatarUrl = await getDecryptedMediaUrl(currentAttachmentPath, IMAGE_JPEG);
if (!decryptedAvatarUrl) {
window.log.warn('Could not decrypt avatar stored locally..');
return;
}
const response = await fetch(decryptedAvatarUrl);
const blob = await response.blob();
const decryptedAvatarData = await blob.arrayBuffer();
if (!decryptedAvatarData?.byteLength) {
window.log.warn('Could not read blob of avatar locally..');
return;
}
const encryptedData = await window.textsecure.crypto.encryptProfile(
decryptedAvatarData,
profileKey
);
const avatarPointer = await FSv2.uploadFileToFsV2(encryptedData);
let fileUrl;
if (!avatarPointer) {
window.log.warn('failed to reupload avatar to fsv2');
return;
}
({ fileUrl } = avatarPointer);
ourConvo.set('avatarPointer', fileUrl);
// this encrypts and save the new avatar and returns a new attachment path
const upgraded = await window.Signal.Migrations.processNewAttachment({
isRaw: true,
data: decryptedAvatarData,
url: fileUrl,
});
const newAvatarPath = upgraded.path;
// Replace our temporary image with the attachment pointer from the server:
ourConvo.set('avatar', null);
const existingHash = ourConvo.get('avatarHash');
const displayName = ourConvo.get('profileName');
// this commits already
await ourConvo.setLokiProfile({ avatar: newAvatarPath, displayName, avatarHash: existingHash });
const newTimestampReupload = Date.now();
await createOrUpdateItem({ id: lastAvatarUploadTimestamp, value: newTimestampReupload });
window.log.info(
`Reuploading avatar finished at ${newTimestampReupload}, newAttachmentPointer ${fileUrl}`
);
}
};
/**
* This function is called only once: on app startup with a logged in user
*/
@ -158,6 +256,7 @@ const doAppStartUp = (dispatch: Dispatch<any>) => {
void OnionPaths.buildNewOnionPathsOneAtATime();
}
void showUnstableAttachmentsDialogIfNeeded();
// init the messageQueue. In the constructor, we add all not send messages
// this call does nothing except calling the constructor, which will continue sending message in the pipeline
void getMessageQueue().processAllPending();
@ -178,6 +277,8 @@ const doAppStartUp = (dispatch: Dispatch<any>) => {
void loadDefaultRooms();
debounce(triggerAvatarReUploadIfNeeded, 200);
// TODO: Investigate the case where we reconnect
const ourKey = UserUtils.getOurPubKeyStrFromCache();
SwarmPolling.getInstance().addPubkey(ourKey);
@ -228,6 +329,11 @@ export const ActionsPanel = () => {
void forceRefreshRandomSnodePool();
}, DAYS * 1);
useInterval(() => {
// this won't be run every days, but if the app stays open for more than 10 days
void triggerAvatarReUploadIfNeeded();
}, DAYS * 1);
return (
<div className="module-left-pane__sections-container">
<Section type={SectionType.Profile} avatarPath={ourPrimaryConversation.avatarPath} />

View file

@ -60,6 +60,7 @@ export type ServerToken = {
};
export const hasSyncedInitialConfigurationItem = 'hasSyncedInitialConfigurationItem';
export const lastAvatarUploadTimestamp = 'lastAvatarUploadTimestamp';
const channelsToMake = {
shutdown,

View file

@ -4,9 +4,14 @@ import { parseStatusCodeFromOnionRequest } from '../opengroup/opengroupV2/OpenGr
import { fromArrayBufferToBase64, fromBase64ToArrayBuffer } from '../session/utils/String';
// tslint:disable-next-line: no-http-string
export const fileServerV2URL = 'http://88.99.175.227';
export const fileServerV2PubKey =
export const oldFileServerV2URL = 'http://88.99.175.227';
export const oldFileServerV2PubKey =
'7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69';
// tslint:disable-next-line: no-http-string
export const fileServerV2URL = 'http://filev2.getsession.org';
export const fileServerV2PubKey =
'da21e1d886c6fbaea313f75298bd64aab03a97ce985b46bb2dad9f2089c8ee59';
export type FileServerV2Request = {
method: 'GET' | 'POST' | 'DELETE' | 'PUT';
@ -14,6 +19,7 @@ export type FileServerV2Request = {
// queryParams are used for post or get, but not the same way
queryParams?: Record<string, any>;
headers?: Record<string, string>;
isOldV2server?: boolean; // to remove in a few days
};
const FILES_ENDPOINT = 'files';
@ -67,7 +73,8 @@ export const uploadFileToFsV2 = async (
* @returns the data as an Uint8Array or null
*/
export const downloadFileFromFSv2 = async (
fileIdOrCompleteUrl: string
fileIdOrCompleteUrl: string,
isOldV2server: boolean
): Promise<ArrayBuffer | null> => {
let fileId = fileIdOrCompleteUrl;
if (!fileIdOrCompleteUrl) {
@ -75,13 +82,19 @@ export const downloadFileFromFSv2 = async (
return null;
}
const completeUrlPrefix = `${fileServerV2URL}/${FILES_ENDPOINT}/`;
if (fileIdOrCompleteUrl.startsWith(completeUrlPrefix)) {
fileId = fileId.substr(completeUrlPrefix.length);
const oldCompleteUrlPrefix = `${oldFileServerV2URL}/${FILES_ENDPOINT}/`;
const newCompleteUrlPrefix = `${fileServerV2URL}/${FILES_ENDPOINT}/`;
if (fileIdOrCompleteUrl.startsWith(newCompleteUrlPrefix)) {
fileId = fileId.substr(newCompleteUrlPrefix.length);
} else if (fileIdOrCompleteUrl.startsWith(oldCompleteUrlPrefix)) {
fileId = fileId.substr(oldCompleteUrlPrefix.length);
}
const request: FileServerV2Request = {
method: 'GET',
endpoint: `${FILES_ENDPOINT}/${fileId}`,
isOldV2server,
};
const result = await sendApiV2Request(request);
@ -119,7 +132,11 @@ export const buildUrl = (request: FileServerV2Request | OpenGroupV2Request): URL
if (isOpenGroupV2Request(request)) {
rawURL = `${request.server}/${request.endpoint}`;
} else {
rawURL = `${fileServerV2URL}/${request.endpoint}`;
if (request.isOldV2server) {
rawURL = `${oldFileServerV2URL}/${request.endpoint}`;
} else {
rawURL = `${fileServerV2URL}/${request.endpoint}`;
}
}
if (request.method === 'GET') {

View file

@ -179,7 +179,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
//start right away the function is called, and wait 1sec before calling it again
this.markRead = _.debounce(this.markReadBouncy, 1000, { leading: true });
// Listening for out-of-band data updates
this.on('ourAvatarChanged', avatar => this.updateAvatarOnPublicChat(avatar));
this.typingRefreshTimer = null;
this.typingPauseTimer = null;
@ -783,23 +782,6 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
return null;
}
public async updateAvatarOnPublicChat({ url, profileKey }: any) {
if (!this.isPublic()) {
return;
}
// Always share avatars on PublicChat
if (profileKey && typeof profileKey !== 'string') {
// eslint-disable-next-line no-param-reassign
// tslint:disable-next-line: no-parameter-reassignment
profileKey = fromArrayBufferToBase64(profileKey);
}
const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer(this.get('server'));
if (!serverAPI) {
return;
}
await serverAPI.setAvatar(url, profileKey);
}
public async bouncyUpdateLastMessage() {
if (!this.id) {
return;
@ -1227,11 +1209,11 @@ export class ConversationModel extends Backbone.Model<ConversationAttributes> {
// Not sure if we care about updating the database
}
public async setProfileAvatar(avatar: any, avatarHash?: string) {
public async setProfileAvatar(avatar: null | { path: string }, avatarHash?: string) {
const profileAvatar = this.get('avatar');
const existingHash = this.get('avatarHash');
let shouldCommit = false;
if (profileAvatar !== avatar) {
if (!_.isEqual(profileAvatar, avatar)) {
this.set({ avatar });
shouldCommit = true;
}

View file

@ -58,7 +58,7 @@ const getDestinationPubKey = async (
}
} else {
// this is a fileServer call
return FSv2.fileServerV2PubKey;
return request.isOldV2server ? FSv2.oldFileServerV2PubKey : FSv2.fileServerV2PubKey;
}
};

View file

@ -23,18 +23,19 @@ export async function downloadAttachment(attachment: any) {
serverUrl
);
// is it an attachment hosted on the file server v2 ?
const defaultFsOldV2 = _.startsWith(serverUrl, FSv2.oldFileServerV2URL);
const defaultFsV2 = _.startsWith(serverUrl, FSv2.fileServerV2URL);
let res: ArrayBuffer | null = null;
if (defaultFsV2) {
if (defaultFsV2 || defaultFsOldV2) {
let attachmentId = attachment.id;
if (!attachmentId) {
// try to get the fileId from the end of the URL
attachmentId = attachment.url;
}
window?.log?.info('Download v2 file server attachment');
res = await FSv2.downloadFileFromFSv2(attachmentId);
res = await FSv2.downloadFileFromFSv2(attachmentId, defaultFsOldV2);
} else {
window.log.warn(
'downloadAttachment attachment is neither opengroup attachment nor fsv2... Dropping it'

View file

@ -47,7 +47,7 @@ describe('Attachment', () => {
contentType: MIME.VIDEO_QUICKTIME,
};
const actual = Attachment.getSuggestedFilename({ attachment });
const expected = 'session-attachment.mov';
const expected = 'funny-cat.mov';
assert.strictEqual(actual, expected);
});
it('should generate a filename without timestamp but with an index', () => {
@ -60,7 +60,7 @@ describe('Attachment', () => {
attachment,
index: 3,
});
const expected = 'session-attachment_003.mov';
const expected = 'funny-cat.mov';
assert.strictEqual(actual, expected);
});
it('should generate a filename with an extension if contentType is not setup', () => {
@ -73,7 +73,7 @@ describe('Attachment', () => {
attachment,
index: 3,
});
const expected = 'session-attachment_003.ini';
const expected = 'funny-cat.ini';
assert.strictEqual(actual, expected);
});
@ -87,7 +87,7 @@ describe('Attachment', () => {
attachment,
index: 3,
});
const expected = 'session-attachment_003.txt';
const expected = 'funny-cat.txt';
assert.strictEqual(actual, expected);
});
it('should generate a filename with an extension if contentType is json', () => {
@ -100,7 +100,7 @@ describe('Attachment', () => {
attachment,
index: 3,
});
const expected = 'session-attachment_003.json';
const expected = 'funny-cat.json';
assert.strictEqual(actual, expected);
});
});
@ -116,12 +116,28 @@ describe('Attachment', () => {
attachment,
timestamp,
});
const expected = 'session-attachment-2000-01-01-000000.mov';
const expected = 'funny-cat.mov';
assert.strictEqual(actual, expected);
});
});
context('for attachment with index', () => {
it('should generate a filename based on timestamp', () => {
it('should generate a filename based on timestamp if filename is not set', () => {
const attachment: Attachment.AttachmentType = {
fileName: '',
url: 'funny-cat.mov',
contentType: MIME.VIDEO_QUICKTIME,
};
const timestamp = new Date(new Date(0).getTimezoneOffset() * 60 * 1000);
const actual = Attachment.getSuggestedFilename({
attachment,
timestamp,
index: 3,
});
const expected = 'session-attachment-1970-01-01-000000_003.mov';
assert.strictEqual(actual, expected);
});
it('should generate a filename based on filename if present', () => {
const attachment: Attachment.AttachmentType = {
fileName: 'funny-cat.mov',
url: 'funny-cat.mov',
@ -133,7 +149,7 @@ describe('Attachment', () => {
timestamp,
index: 3,
});
const expected = 'session-attachment-1970-01-01-000000_003.mov';
const expected = 'funny-cat.mov';
assert.strictEqual(actual, expected);
});
});

View file

@ -334,6 +334,9 @@ export const getSuggestedFilename = ({
timestamp?: number | Date;
index?: number;
}): string => {
if (attachment.fileName?.length > 3) {
return attachment.fileName;
}
const prefix = 'session-attachment';
const suffix = timestamp ? moment(timestamp).format('-YYYY-MM-DD-HHmmss') : '';
const fileType = getFileExtension(attachment);