mirror of
https://github.com/oxen-io/session-desktop.git
synced 2023-12-14 02:12:57 +01:00
allow fileserverv2 attachments to be downloaded, upload disabled
This commit is contained in:
parent
64eab5160d
commit
2b576de2cd
131
ts/fileserver/FileServerApiV2.ts
Normal file
131
ts/fileserver/FileServerApiV2.ts
Normal file
|
@ -0,0 +1,131 @@
|
|||
import { OpenGroupV2Request } from '../opengroup/opengroupV2/ApiUtil';
|
||||
import { sendApiV2Request } from '../opengroup/opengroupV2/OpenGroupAPIV2';
|
||||
import { parseStatusCodeFromOnionRequest } from '../opengroup/opengroupV2/OpenGroupAPIV2Parser';
|
||||
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 =
|
||||
'7cb31905b55cd5580c686911debf672577b3fb0bff81df4ce2d5c4cb3a7aaa69';
|
||||
|
||||
export type FileServerV2Request = {
|
||||
method: 'GET' | 'POST' | 'DELETE' | 'PUT';
|
||||
endpoint: string;
|
||||
// queryParams are used for post or get, but not the same way
|
||||
queryParams?: Record<string, any>;
|
||||
headers?: Record<string, string>;
|
||||
};
|
||||
|
||||
const FILES_ENDPOINT = 'files';
|
||||
|
||||
// Disable this if you don't want to use the file server v2 for sending
|
||||
// Receiving is always enabled if the attachments url matches a fsv2 url
|
||||
export const useFileServerAPIV2Sending = false;
|
||||
|
||||
/**
|
||||
* Upload a file to the file server v2
|
||||
* @param fileContent the data to send
|
||||
* @returns null or the fileID and complete URL to share this file
|
||||
*/
|
||||
export const uploadFileToFsV2 = async (
|
||||
fileContent: ArrayBuffer
|
||||
): Promise<{ fileId: number; fileUrl: string } | null> => {
|
||||
if (!fileContent || !fileContent.byteLength) {
|
||||
return null;
|
||||
}
|
||||
const queryParams = {
|
||||
file: fromArrayBufferToBase64(fileContent),
|
||||
};
|
||||
|
||||
const request: FileServerV2Request = {
|
||||
method: 'POST',
|
||||
endpoint: FILES_ENDPOINT,
|
||||
queryParams,
|
||||
};
|
||||
|
||||
const result = await sendApiV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
if (statusCode !== 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// we should probably change the logic of sendOnionRequest to not have all those levels
|
||||
const fileId = (result as any)?.result?.result as number | undefined;
|
||||
if (!fileId) {
|
||||
return null;
|
||||
}
|
||||
const fileUrl = `${fileServerV2URL}/${FILES_ENDPOINT}/${fileId}`;
|
||||
return {
|
||||
fileId: fileId,
|
||||
fileUrl,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Download a file given the fileId from the fileserver v2
|
||||
* @param fileId the fileId to download
|
||||
* @returns the data as an Uint8Array or null
|
||||
*/
|
||||
export const downloadFileFromFSv2 = async (fileId: string): Promise<ArrayBuffer | null> => {
|
||||
if (!fileId) {
|
||||
window.log.warn('');
|
||||
return null;
|
||||
}
|
||||
const request: FileServerV2Request = {
|
||||
method: 'GET',
|
||||
endpoint: `${FILES_ENDPOINT}/${fileId}`,
|
||||
};
|
||||
|
||||
const result = await sendApiV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
if (statusCode !== 200) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// we should probably change the logic of sendOnionRequest to not have all those levels
|
||||
const base64Data = (result as any)?.result?.result as string | undefined;
|
||||
|
||||
if (!base64Data) {
|
||||
return null;
|
||||
}
|
||||
return fromBase64ToArrayBuffer(base64Data);
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a typescript type guard
|
||||
* request.isAuthRequired Must be set for an OpenGroupV2Request
|
||||
* @returns true if request.isAuthRequired is not undefined
|
||||
*/
|
||||
export function isOpenGroupV2Request(
|
||||
request: FileServerV2Request | OpenGroupV2Request
|
||||
): request is OpenGroupV2Request {
|
||||
return (request as OpenGroupV2Request).isAuthRequired !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to build an full url and check it for validity.
|
||||
* @returns null if the check failed. the built URL otherwise
|
||||
*/
|
||||
export const buildUrl = (request: FileServerV2Request | OpenGroupV2Request): URL | null => {
|
||||
let rawURL: string;
|
||||
if (isOpenGroupV2Request(request)) {
|
||||
rawURL = `${request.server}/${request.endpoint}`;
|
||||
} else {
|
||||
rawURL = `${fileServerV2URL}/${request.endpoint}`;
|
||||
}
|
||||
|
||||
if (request.method === 'GET') {
|
||||
const entries = Object.entries(request.queryParams || {});
|
||||
|
||||
if (entries.length) {
|
||||
const queryString = entries.map(([key, value]) => `${key}=${value}`).join('&');
|
||||
rawURL += `?${queryString}`;
|
||||
}
|
||||
}
|
||||
// this just check that the URL is valid
|
||||
try {
|
||||
return new URL(`${rawURL}`);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
4
ts/fileserver/index.ts
Normal file
4
ts/fileserver/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import * as FSv2 from './FileServerApiV2';
|
||||
|
||||
// fsv2 = File server V2
|
||||
export { FSv2 };
|
161
ts/opengroup/opengroupV2/ApiAuth.ts
Normal file
161
ts/opengroup/opengroupV2/ApiAuth.ts
Normal file
|
@ -0,0 +1,161 @@
|
|||
import { getV2OpenGroupRoomByRoomId, saveV2OpenGroupRoom } from '../../data/opengroups';
|
||||
import { allowOnlyOneAtATime } from '../../session/utils/Promise';
|
||||
import { fromBase64ToArrayBuffer, toHex } from '../../session/utils/String';
|
||||
import { getIdentityKeyPair, getOurPubKeyStrFromCache } from '../../session/utils/User';
|
||||
import { OpenGroupRequestCommonType, OpenGroupV2Request } from './ApiUtil';
|
||||
import { sendApiV2Request } from './OpenGroupAPIV2';
|
||||
import { parseStatusCodeFromOnionRequest } from './OpenGroupAPIV2Parser';
|
||||
|
||||
async function claimAuthToken(
|
||||
authToken: string,
|
||||
serverUrl: string,
|
||||
roomId: string
|
||||
): Promise<string | null> {
|
||||
// Set explicitly here because is isn't in the database yet at this point
|
||||
const headers = { Authorization: authToken };
|
||||
const request: OpenGroupV2Request = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
room: roomId,
|
||||
server: serverUrl,
|
||||
queryParams: { public_key: getOurPubKeyStrFromCache() },
|
||||
isAuthRequired: false,
|
||||
endpoint: 'claim_auth_token',
|
||||
};
|
||||
const result = await sendApiV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
if (statusCode !== 200) {
|
||||
window.log.warn(`Could not claim token, status code: ${statusCode}`);
|
||||
return null;
|
||||
}
|
||||
return authToken;
|
||||
}
|
||||
|
||||
export async function getAuthToken({
|
||||
serverUrl,
|
||||
roomId,
|
||||
}: OpenGroupRequestCommonType): Promise<string | null> {
|
||||
// first try to fetch from db a saved token.
|
||||
const roomDetails = await getV2OpenGroupRoomByRoomId({ serverUrl, roomId });
|
||||
if (!roomDetails) {
|
||||
window.log.warn('getAuthToken Room does not exist.');
|
||||
return null;
|
||||
}
|
||||
if (roomDetails?.token) {
|
||||
return roomDetails.token;
|
||||
}
|
||||
|
||||
await allowOnlyOneAtATime(`getAuthTokenV2${serverUrl}:${roomId}`, async () => {
|
||||
try {
|
||||
window.log.info('TRIGGERING NEW AUTH TOKEN WITH', { serverUrl, roomId });
|
||||
const token = await requestNewAuthToken({ serverUrl, roomId });
|
||||
if (!token) {
|
||||
window.log.warn('invalid new auth token', token);
|
||||
return;
|
||||
}
|
||||
const claimedToken = await claimAuthToken(token, serverUrl, roomId);
|
||||
if (!claimedToken) {
|
||||
window.log.warn('invalid claimed token', claimedToken);
|
||||
}
|
||||
// still save it to the db. just to mark it as to be refreshed later
|
||||
roomDetails.token = claimedToken || '';
|
||||
await saveV2OpenGroupRoom(roomDetails);
|
||||
} catch (e) {
|
||||
window.log.error('Failed to getAuthToken', e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
const refreshedRoomDetails = await getV2OpenGroupRoomByRoomId({
|
||||
serverUrl,
|
||||
roomId,
|
||||
});
|
||||
if (!refreshedRoomDetails) {
|
||||
window.log.warn('getAuthToken Room does not exist.');
|
||||
return null;
|
||||
}
|
||||
if (refreshedRoomDetails?.token) {
|
||||
return refreshedRoomDetails?.token;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const deleteAuthToken = async ({
|
||||
serverUrl,
|
||||
roomId,
|
||||
}: OpenGroupRequestCommonType): Promise<boolean> => {
|
||||
const request: OpenGroupV2Request = {
|
||||
method: 'DELETE',
|
||||
room: roomId,
|
||||
server: serverUrl,
|
||||
isAuthRequired: false,
|
||||
endpoint: 'auth_token',
|
||||
};
|
||||
try {
|
||||
const result = await sendApiV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
if (statusCode !== 200) {
|
||||
window.log.warn(`Could not deleteAuthToken, status code: ${statusCode}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
window.log.error('deleteAuthToken failed:', e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// tslint:disable: member-ordering
|
||||
export async function requestNewAuthToken({
|
||||
serverUrl,
|
||||
roomId,
|
||||
}: OpenGroupRequestCommonType): Promise<string | null> {
|
||||
const userKeyPair = await getIdentityKeyPair();
|
||||
if (!userKeyPair) {
|
||||
throw new Error('Failed to fetch user keypair');
|
||||
}
|
||||
|
||||
const ourPubkey = getOurPubKeyStrFromCache();
|
||||
const parameters = {} as Record<string, string>;
|
||||
parameters.public_key = ourPubkey;
|
||||
const request: OpenGroupV2Request = {
|
||||
method: 'GET',
|
||||
room: roomId,
|
||||
server: serverUrl,
|
||||
queryParams: parameters,
|
||||
isAuthRequired: false,
|
||||
endpoint: 'auth_token_challenge',
|
||||
};
|
||||
const json = (await sendApiV2Request(request)) as any;
|
||||
// parse the json
|
||||
if (!json || !json?.result?.challenge) {
|
||||
window.log.warn('Parsing failed');
|
||||
return null;
|
||||
}
|
||||
const {
|
||||
ciphertext: base64EncodedCiphertext,
|
||||
ephemeral_public_key: base64EncodedEphemeralPublicKey,
|
||||
} = json?.result?.challenge;
|
||||
|
||||
if (!base64EncodedCiphertext || !base64EncodedEphemeralPublicKey) {
|
||||
window.log.warn('Parsing failed');
|
||||
return null;
|
||||
}
|
||||
const ciphertext = fromBase64ToArrayBuffer(base64EncodedCiphertext);
|
||||
const ephemeralPublicKey = fromBase64ToArrayBuffer(base64EncodedEphemeralPublicKey);
|
||||
try {
|
||||
const symmetricKey = await window.libloki.crypto.deriveSymmetricKey(
|
||||
ephemeralPublicKey,
|
||||
userKeyPair.privKey
|
||||
);
|
||||
|
||||
const plaintextBuffer = await window.libloki.crypto.DecryptAESGCM(symmetricKey, ciphertext);
|
||||
|
||||
const token = toHex(plaintextBuffer);
|
||||
|
||||
return token;
|
||||
} catch (e) {
|
||||
window.log.error('Failed to decrypt token open group v2');
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import _ from 'underscore';
|
||||
import { FileServerV2Request } from '../../fileserver/FileServerApiV2';
|
||||
import { PubKey } from '../../session/types';
|
||||
import { allowOnlyOneAtATime } from '../../session/utils/Promise';
|
||||
import { fromBase64ToArrayBuffer, fromHex } from '../../session/utils/String';
|
||||
|
@ -13,14 +14,9 @@ export type OpenGroupRequestCommonType = {
|
|||
roomId: string;
|
||||
};
|
||||
|
||||
export type OpenGroupV2Request = {
|
||||
method: 'GET' | 'POST' | 'DELETE' | 'PUT';
|
||||
export type OpenGroupV2Request = FileServerV2Request & {
|
||||
room: string;
|
||||
server: string;
|
||||
endpoint: string;
|
||||
// queryParams are used for post or get, but not the same way
|
||||
queryParams?: Record<string, any>;
|
||||
headers?: Record<string, string>;
|
||||
isAuthRequired: boolean;
|
||||
serverPublicKey?: string; // if not provided, a db called will be made to try to get it.
|
||||
};
|
||||
|
@ -43,28 +39,6 @@ export type OpenGroupV2InfoJoinable = OpenGroupV2Info & {
|
|||
base64Data?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Try to build an full url and check it for validity.
|
||||
* @returns null if the check failed. the built URL otherwise
|
||||
*/
|
||||
export const buildUrl = (request: OpenGroupV2Request): URL | null => {
|
||||
let rawURL = `${request.server}/${request.endpoint}`;
|
||||
if (request.method === 'GET') {
|
||||
const entries = Object.entries(request.queryParams || {});
|
||||
|
||||
if (entries.length) {
|
||||
const queryString = entries.map(([key, value]) => `${key}=${value}`).join('&');
|
||||
rawURL += `?${queryString}`;
|
||||
}
|
||||
}
|
||||
// this just check that the URL is valid
|
||||
try {
|
||||
return new URL(`${rawURL}`);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const parseMessages = async (
|
||||
rawMessages: Array<Record<string, any>>
|
||||
): Promise<Array<OpenGroupMessageV2>> => {
|
||||
|
|
|
@ -4,24 +4,11 @@ import {
|
|||
OpenGroupV2Room,
|
||||
saveV2OpenGroupRoom,
|
||||
} from '../../data/opengroups';
|
||||
import { ConversationController } from '../../session/conversations';
|
||||
import { FSv2 } from '../../fileserver/';
|
||||
import { sendViaOnion } from '../../session/onions/onionSend';
|
||||
import { PubKey } from '../../session/types';
|
||||
import { allowOnlyOneAtATime } from '../../session/utils/Promise';
|
||||
import {
|
||||
fromArrayBufferToBase64,
|
||||
fromBase64ToArrayBuffer,
|
||||
toHex,
|
||||
} from '../../session/utils/String';
|
||||
import { getIdentityKeyPair, getOurPubKeyStrFromCache } from '../../session/utils/User';
|
||||
import { getCompleteEndpointUrl, getOpenGroupV2ConversationId } from '../utils/OpenGroupUtils';
|
||||
import {
|
||||
buildUrl,
|
||||
OpenGroupRequestCommonType,
|
||||
OpenGroupV2Info,
|
||||
OpenGroupV2Request,
|
||||
parseMessages,
|
||||
} from './ApiUtil';
|
||||
import { fromArrayBufferToBase64, fromBase64ToArrayBuffer } from '../../session/utils/String';
|
||||
import { OpenGroupRequestCommonType, OpenGroupV2Info, OpenGroupV2Request } from './ApiUtil';
|
||||
import {
|
||||
parseMemberCount,
|
||||
parseRooms,
|
||||
|
@ -29,13 +16,63 @@ import {
|
|||
} from './OpenGroupAPIV2Parser';
|
||||
import { OpenGroupMessageV2 } from './OpenGroupMessageV2';
|
||||
|
||||
import { isOpenGroupV2Request } from '../../fileserver/FileServerApiV2';
|
||||
import { getAuthToken } from './ApiAuth';
|
||||
|
||||
/**
|
||||
* This send function is to be used for all non polling stuff
|
||||
* download and upload of attachments for instance, but most of the logic happens in
|
||||
* the compact_poll endpoint
|
||||
* This function returns a base url to this room
|
||||
* This is basically used for building url after posting an attachment
|
||||
* hasRoomInEndpoint = true means the roomId is already in the endpoint.
|
||||
* so we don't add the room after the serverUrl.
|
||||
*
|
||||
*/
|
||||
async function sendOpenGroupV2Request(request: OpenGroupV2Request): Promise<Object | null> {
|
||||
const builtUrl = buildUrl(request);
|
||||
function getCompleteEndpointUrl(
|
||||
roomInfos: OpenGroupRequestCommonType,
|
||||
endpoint: string,
|
||||
hasRoomInEndpoint: boolean
|
||||
) {
|
||||
// serverUrl has the port and protocol already
|
||||
if (!hasRoomInEndpoint) {
|
||||
return `${roomInfos.serverUrl}/${roomInfos.roomId}/${endpoint}`;
|
||||
}
|
||||
// not room based, the endpoint already has the room in it
|
||||
return `${roomInfos.serverUrl}/${endpoint}`;
|
||||
}
|
||||
|
||||
const getDestinationPubKey = async (
|
||||
request: OpenGroupV2Request | FSv2.FileServerV2Request
|
||||
): Promise<string> => {
|
||||
if (FSv2.isOpenGroupV2Request(request)) {
|
||||
if (!request.serverPublicKey) {
|
||||
const roomDetails = await getV2OpenGroupRoomByRoomId({
|
||||
serverUrl: request.server,
|
||||
roomId: request.room,
|
||||
});
|
||||
if (!roomDetails?.serverPublicKey) {
|
||||
throw new Error('PublicKey not found for this server.');
|
||||
}
|
||||
return roomDetails.serverPublicKey;
|
||||
} else {
|
||||
return request.serverPublicKey;
|
||||
}
|
||||
} else {
|
||||
// this is a fileServer call
|
||||
return FSv2.fileServerV2PubKey;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* This send function is to be used for all non polling stuff.
|
||||
* This function can be used for OpengroupV2 request OR File Server V2 request
|
||||
* Download and upload of attachments for instance, but most of the logic happens in
|
||||
* the compact_poll endpoint.
|
||||
*
|
||||
*/
|
||||
export async function sendApiV2Request(
|
||||
request: OpenGroupV2Request | FSv2.FileServerV2Request
|
||||
): Promise<Object | null> {
|
||||
const builtUrl = FSv2.buildUrl(request);
|
||||
|
||||
if (!builtUrl) {
|
||||
throw new Error('Invalid request');
|
||||
|
@ -43,28 +80,19 @@ async function sendOpenGroupV2Request(request: OpenGroupV2Request): Promise<Obje
|
|||
|
||||
// set the headers sent by the caller, and the roomId.
|
||||
const headers = request.headers || {};
|
||||
headers.Room = request.room;
|
||||
if (FSv2.isOpenGroupV2Request(request)) {
|
||||
headers.Room = request.room;
|
||||
}
|
||||
|
||||
let body = '';
|
||||
if (request.method !== 'GET') {
|
||||
body = JSON.stringify(request.queryParams);
|
||||
}
|
||||
|
||||
let destinationX25519Key: string;
|
||||
if (!request.serverPublicKey) {
|
||||
const roomDetails = await getV2OpenGroupRoomByRoomId({
|
||||
serverUrl: request.server,
|
||||
roomId: request.room,
|
||||
});
|
||||
if (!roomDetails?.serverPublicKey) {
|
||||
throw new Error('PublicKey not found for this server.');
|
||||
}
|
||||
destinationX25519Key = roomDetails.serverPublicKey;
|
||||
} else {
|
||||
destinationX25519Key = request.serverPublicKey;
|
||||
}
|
||||
const destinationX25519Key = await getDestinationPubKey(request);
|
||||
|
||||
// Because auth happens on a per-room basis, we need both to make an authenticated request
|
||||
if (request.isAuthRequired && request.room) {
|
||||
if (isOpenGroupV2Request(request) && request.isAuthRequired && request.room) {
|
||||
// this call will either return the token on the db,
|
||||
// or the promise currently fetching a new token for that same room
|
||||
// or fetch from the open group a new token for that room.
|
||||
|
@ -122,61 +150,6 @@ async function sendOpenGroupV2Request(request: OpenGroupV2Request): Promise<Obje
|
|||
}
|
||||
}
|
||||
|
||||
// tslint:disable: member-ordering
|
||||
export async function requestNewAuthToken({
|
||||
serverUrl,
|
||||
roomId,
|
||||
}: OpenGroupRequestCommonType): Promise<string | null> {
|
||||
const userKeyPair = await getIdentityKeyPair();
|
||||
if (!userKeyPair) {
|
||||
throw new Error('Failed to fetch user keypair');
|
||||
}
|
||||
|
||||
const ourPubkey = getOurPubKeyStrFromCache();
|
||||
const parameters = {} as Record<string, string>;
|
||||
parameters.public_key = ourPubkey;
|
||||
const request: OpenGroupV2Request = {
|
||||
method: 'GET',
|
||||
room: roomId,
|
||||
server: serverUrl,
|
||||
queryParams: parameters,
|
||||
isAuthRequired: false,
|
||||
endpoint: 'auth_token_challenge',
|
||||
};
|
||||
const json = (await sendOpenGroupV2Request(request)) as any;
|
||||
// parse the json
|
||||
if (!json || !json?.result?.challenge) {
|
||||
window.log.warn('Parsing failed');
|
||||
return null;
|
||||
}
|
||||
const {
|
||||
ciphertext: base64EncodedCiphertext,
|
||||
ephemeral_public_key: base64EncodedEphemeralPublicKey,
|
||||
} = json?.result?.challenge;
|
||||
|
||||
if (!base64EncodedCiphertext || !base64EncodedEphemeralPublicKey) {
|
||||
window.log.warn('Parsing failed');
|
||||
return null;
|
||||
}
|
||||
const ciphertext = fromBase64ToArrayBuffer(base64EncodedCiphertext);
|
||||
const ephemeralPublicKey = fromBase64ToArrayBuffer(base64EncodedEphemeralPublicKey);
|
||||
try {
|
||||
const symmetricKey = await window.libloki.crypto.deriveSymmetricKey(
|
||||
ephemeralPublicKey,
|
||||
userKeyPair.privKey
|
||||
);
|
||||
|
||||
const plaintextBuffer = await window.libloki.crypto.DecryptAESGCM(symmetricKey, ciphertext);
|
||||
|
||||
const token = toHex(plaintextBuffer);
|
||||
|
||||
return token;
|
||||
} catch (e) {
|
||||
window.log.error('Failed to decrypt token open group v2');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -194,7 +167,7 @@ export async function openGroupV2GetRoomInfo({
|
|||
isAuthRequired: false,
|
||||
endpoint: `rooms/${roomId}`,
|
||||
};
|
||||
const result = (await sendOpenGroupV2Request(request)) as any;
|
||||
const result = (await sendApiV2Request(request)) as any;
|
||||
if (result?.result?.room) {
|
||||
const { id, name, image_id: imageId } = result?.result?.room;
|
||||
|
||||
|
@ -214,105 +187,6 @@ export async function openGroupV2GetRoomInfo({
|
|||
return null;
|
||||
}
|
||||
|
||||
async function claimAuthToken(
|
||||
authToken: string,
|
||||
serverUrl: string,
|
||||
roomId: string
|
||||
): Promise<string | null> {
|
||||
// Set explicitly here because is isn't in the database yet at this point
|
||||
const headers = { Authorization: authToken };
|
||||
const request: OpenGroupV2Request = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
room: roomId,
|
||||
server: serverUrl,
|
||||
queryParams: { public_key: getOurPubKeyStrFromCache() },
|
||||
isAuthRequired: false,
|
||||
endpoint: 'claim_auth_token',
|
||||
};
|
||||
const result = await sendOpenGroupV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
if (statusCode !== 200) {
|
||||
window.log.warn(`Could not claim token, status code: ${statusCode}`);
|
||||
return null;
|
||||
}
|
||||
return authToken;
|
||||
}
|
||||
|
||||
export async function getAuthToken({
|
||||
serverUrl,
|
||||
roomId,
|
||||
}: OpenGroupRequestCommonType): Promise<string | null> {
|
||||
// first try to fetch from db a saved token.
|
||||
const roomDetails = await getV2OpenGroupRoomByRoomId({ serverUrl, roomId });
|
||||
if (!roomDetails) {
|
||||
window.log.warn('getAuthToken Room does not exist.');
|
||||
return null;
|
||||
}
|
||||
if (roomDetails?.token) {
|
||||
return roomDetails.token;
|
||||
}
|
||||
|
||||
await allowOnlyOneAtATime(`getAuthTokenV2${serverUrl}:${roomId}`, async () => {
|
||||
try {
|
||||
window.log.info('TRIGGERING NEW AUTH TOKEN WITH', { serverUrl, roomId });
|
||||
const token = await requestNewAuthToken({ serverUrl, roomId });
|
||||
if (!token) {
|
||||
window.log.warn('invalid new auth token', token);
|
||||
return;
|
||||
}
|
||||
const claimedToken = await claimAuthToken(token, serverUrl, roomId);
|
||||
if (!claimedToken) {
|
||||
window.log.warn('invalid claimed token', claimedToken);
|
||||
}
|
||||
// still save it to the db. just to mark it as to be refreshed later
|
||||
roomDetails.token = claimedToken || '';
|
||||
await saveV2OpenGroupRoom(roomDetails);
|
||||
} catch (e) {
|
||||
window.log.error('Failed to getAuthToken', e);
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
const refreshedRoomDetails = await getV2OpenGroupRoomByRoomId({
|
||||
serverUrl,
|
||||
roomId,
|
||||
});
|
||||
if (!refreshedRoomDetails) {
|
||||
window.log.warn('getAuthToken Room does not exist.');
|
||||
return null;
|
||||
}
|
||||
if (refreshedRoomDetails?.token) {
|
||||
return refreshedRoomDetails?.token;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const deleteAuthToken = async ({
|
||||
serverUrl,
|
||||
roomId,
|
||||
}: OpenGroupRequestCommonType): Promise<boolean> => {
|
||||
const request: OpenGroupV2Request = {
|
||||
method: 'DELETE',
|
||||
room: roomId,
|
||||
server: serverUrl,
|
||||
isAuthRequired: false,
|
||||
endpoint: 'auth_token',
|
||||
};
|
||||
try {
|
||||
const result = await sendOpenGroupV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
if (statusCode !== 200) {
|
||||
window.log.warn(`Could not deleteAuthToken, status code: ${statusCode}`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
window.log.error('deleteAuthToken failed:', e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send the specified message to the specified room.
|
||||
* If an error happens, this function throws it
|
||||
|
@ -333,7 +207,7 @@ export const postMessage = async (
|
|||
isAuthRequired: true,
|
||||
endpoint: 'messages',
|
||||
};
|
||||
const result = await sendOpenGroupV2Request(request);
|
||||
const result = await sendApiV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
|
||||
if (statusCode !== 200) {
|
||||
|
@ -360,7 +234,7 @@ export const banUser = async (
|
|||
queryParams,
|
||||
endpoint: 'block_list',
|
||||
};
|
||||
const banResult = await sendOpenGroupV2Request(request);
|
||||
const banResult = await sendApiV2Request(request);
|
||||
const isOk = parseStatusCodeFromOnionRequest(banResult) === 200;
|
||||
return isOk;
|
||||
};
|
||||
|
@ -376,7 +250,7 @@ export const unbanUser = async (
|
|||
isAuthRequired: true,
|
||||
endpoint: `block_list/${userToBan.key}`,
|
||||
};
|
||||
const unbanResult = await sendOpenGroupV2Request(request);
|
||||
const unbanResult = await sendApiV2Request(request);
|
||||
const isOk = parseStatusCodeFromOnionRequest(unbanResult) === 200;
|
||||
return isOk;
|
||||
};
|
||||
|
@ -393,7 +267,7 @@ export const deleteMessageByServerIds = async (
|
|||
endpoint: 'delete_messages',
|
||||
queryParams: { ids: idsToRemove },
|
||||
};
|
||||
const messageDeletedResult = await sendOpenGroupV2Request(request);
|
||||
const messageDeletedResult = await sendApiV2Request(request);
|
||||
const isOk = parseStatusCodeFromOnionRequest(messageDeletedResult) === 200;
|
||||
return isOk;
|
||||
};
|
||||
|
@ -408,7 +282,7 @@ export const getAllRoomInfos = async (roomInfos: OpenGroupV2Room) => {
|
|||
endpoint: 'rooms',
|
||||
serverPublicKey: roomInfos.serverPublicKey,
|
||||
};
|
||||
const result = await sendOpenGroupV2Request(request);
|
||||
const result = await sendApiV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
|
||||
if (statusCode !== 200) {
|
||||
|
@ -429,7 +303,7 @@ export const getMemberCount = async (
|
|||
isAuthRequired: true,
|
||||
endpoint: 'member_count',
|
||||
};
|
||||
const result = await sendOpenGroupV2Request(request);
|
||||
const result = await sendApiV2Request(request);
|
||||
if (parseStatusCodeFromOnionRequest(result) !== 200) {
|
||||
window.log.warn('getMemberCount failed invalid status code');
|
||||
return;
|
||||
|
@ -463,7 +337,7 @@ export const downloadFileOpenGroupV2 = async (
|
|||
endpoint: `files/${fileId}`,
|
||||
};
|
||||
|
||||
const result = await sendOpenGroupV2Request(request);
|
||||
const result = await sendApiV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
if (statusCode !== 200) {
|
||||
return null;
|
||||
|
@ -490,7 +364,7 @@ export const downloadFileOpenGroupV2ByUrl = async (
|
|||
endpoint: pathName,
|
||||
};
|
||||
|
||||
const result = await sendOpenGroupV2Request(request);
|
||||
const result = await sendApiV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
if (statusCode !== 200) {
|
||||
return null;
|
||||
|
@ -522,7 +396,7 @@ export const downloadPreviewOpenGroupV2 = async (
|
|||
serverPublicKey: roomInfos.serverPublicKey,
|
||||
};
|
||||
|
||||
const result = await sendOpenGroupV2Request(request);
|
||||
const result = await sendApiV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
if (statusCode !== 200) {
|
||||
return null;
|
||||
|
@ -561,7 +435,7 @@ export const uploadFileOpenGroupV2 = async (
|
|||
queryParams,
|
||||
};
|
||||
|
||||
const result = await sendOpenGroupV2Request(request);
|
||||
const result = await sendApiV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
if (statusCode !== 200) {
|
||||
return null;
|
||||
|
@ -601,7 +475,7 @@ export const uploadImageForRoomOpenGroupV2 = async (
|
|||
queryParams,
|
||||
};
|
||||
|
||||
const result = await sendOpenGroupV2Request(request);
|
||||
const result = await sendApiV2Request(request);
|
||||
const statusCode = parseStatusCodeFromOnionRequest(result);
|
||||
if (statusCode !== 200) {
|
||||
return null;
|
||||
|
@ -626,7 +500,7 @@ export const addModerator = async (
|
|||
queryParams: { public_key: userToAddAsMods.key, room_id: roomInfos.roomId },
|
||||
endpoint: 'moderators',
|
||||
};
|
||||
const addModResult = await sendOpenGroupV2Request(request);
|
||||
const addModResult = await sendApiV2Request(request);
|
||||
const isOk = parseStatusCodeFromOnionRequest(addModResult) === 200;
|
||||
return isOk;
|
||||
};
|
||||
|
@ -642,7 +516,7 @@ export const removeModerator = async (
|
|||
isAuthRequired: true,
|
||||
endpoint: `moderators/${userToAddAsMods.key}`,
|
||||
};
|
||||
const removeModResult = await sendOpenGroupV2Request(request);
|
||||
const removeModResult = await sendApiV2Request(request);
|
||||
const isOk = parseStatusCodeFromOnionRequest(removeModResult) === 200;
|
||||
return isOk;
|
||||
};
|
||||
|
|
|
@ -8,7 +8,8 @@ import { parseStatusCodeFromOnionRequest } from './OpenGroupAPIV2Parser';
|
|||
import _ from 'lodash';
|
||||
import { sendViaOnion } from '../../session/onions/onionSend';
|
||||
import { OpenGroupMessageV2 } from './OpenGroupMessageV2';
|
||||
import { downloadPreviewOpenGroupV2, getAuthToken, getMemberCount } from './OpenGroupAPIV2';
|
||||
import { downloadPreviewOpenGroupV2, getMemberCount } from './OpenGroupAPIV2';
|
||||
import { getAuthToken } from './ApiAuth';
|
||||
|
||||
const COMPACT_POLL_ENDPOINT = 'compact_poll';
|
||||
|
||||
|
@ -247,7 +248,7 @@ async function sendOpenGroupV2RequestCompactPoll(
|
|||
|
||||
const statusCode = parseStatusCodeFromOnionRequest(res);
|
||||
if (!statusCode) {
|
||||
window.log.warn('sendOpenGroupV2Request Got unknown status code; res:', res);
|
||||
window.log.warn('sendOpenGroupV2RequestCompactPoll Got unknown status code; res:', res);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,10 +10,11 @@ import { ConversationController } from '../../session/conversations';
|
|||
import { allowOnlyOneAtATime } from '../../session/utils/Promise';
|
||||
import { getOpenGroupV2ConversationId } from '../utils/OpenGroupUtils';
|
||||
import { OpenGroupRequestCommonType } from './ApiUtil';
|
||||
import { deleteAuthToken, openGroupV2GetRoomInfo } from './OpenGroupAPIV2';
|
||||
import { openGroupV2GetRoomInfo } from './OpenGroupAPIV2';
|
||||
import { OpenGroupServerPoller } from './OpenGroupServerPoller';
|
||||
|
||||
import _ from 'lodash';
|
||||
import { deleteAuthToken } from './ApiAuth';
|
||||
|
||||
export class OpenGroupManagerV2 {
|
||||
public static readonly useV2OpenGroups = false;
|
||||
|
|
|
@ -65,26 +65,6 @@ export function getCompleteUrlFromRoom(roomInfos: OpenGroupV2Room) {
|
|||
return `${roomInfos.serverUrl}/${roomInfos.roomId}?${publicKeyParam}${roomInfos.serverPublicKey}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns a base url to this room
|
||||
* This is basically used for building url after posting an attachment
|
||||
* hasRoomInEndpoint = true means the roomId is already in the endpoint.
|
||||
* so we don't add the room after the serverUrl.
|
||||
*
|
||||
*/
|
||||
export function getCompleteEndpointUrl(
|
||||
roomInfos: OpenGroupRequestCommonType,
|
||||
endpoint: string,
|
||||
hasRoomInEndpoint: boolean
|
||||
) {
|
||||
// serverUrl has the port and protocol already
|
||||
if (!hasRoomInEndpoint) {
|
||||
return `${roomInfos.serverUrl}/${roomInfos.roomId}/${endpoint}`;
|
||||
}
|
||||
// not room based, the endpoint already has the room in it
|
||||
return `${roomInfos.serverUrl}/${endpoint}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to establish a connection with the specified open group url.
|
||||
*
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
downloadFileOpenGroupV2ByUrl,
|
||||
} from '../opengroup/opengroupV2/OpenGroupAPIV2';
|
||||
import { OpenGroupRequestCommonType } from '../opengroup/opengroupV2/ApiUtil';
|
||||
import { FSv2 } from '../fileserver';
|
||||
|
||||
export async function downloadAttachment(attachment: any) {
|
||||
const serverUrl = new URL(attachment.url).origin;
|
||||
|
@ -19,11 +20,20 @@ export async function downloadAttachment(attachment: any) {
|
|||
['https://file-static.lokinet.org', 'https://file.getsession.org'],
|
||||
serverUrl
|
||||
);
|
||||
// is it an attachment hosted on the file server v2 ?
|
||||
const defaultFsV2 = _.startsWith(serverUrl, FSv2.fileServerV2URL);
|
||||
|
||||
let res: ArrayBuffer | null = null;
|
||||
|
||||
// TODO: we need attachments to remember which API should be used to retrieve them
|
||||
if (!defaultFileserver) {
|
||||
if (defaultFsV2) {
|
||||
if (!attachment.id) {
|
||||
window.log.warn('Cannot download fsv2 file with empty id');
|
||||
return;
|
||||
}
|
||||
window.log.info('Download v2 file server attachment');
|
||||
res = await FSv2.downloadFileFromFSv2(attachment.id);
|
||||
} else if (!defaultFileserver) {
|
||||
// TODO: we need attachments to remember which API should be used to retrieve them
|
||||
const serverAPI = await window.lokiPublicChatAPI.findOrCreateServer(serverUrl);
|
||||
|
||||
if (serverAPI) {
|
||||
|
@ -41,18 +51,6 @@ export async function downloadAttachment(attachment: any) {
|
|||
throw new Error(`Failed to download attachment. Length is 0 for ${attachment.url}`);
|
||||
}
|
||||
|
||||
// FIXME "178" test to remove once this is fixed server side.
|
||||
if (!window.lokiFeatureFlags.useFileOnionRequestsV2) {
|
||||
if (res.byteLength === 178) {
|
||||
window.log.error(
|
||||
'Data of 178 length corresponds of a 404 returned as 200 by file.getsession.org.'
|
||||
);
|
||||
throw new Error(`downloadAttachment: invalid response for ${attachment.url}`);
|
||||
}
|
||||
} else {
|
||||
// if useFileOnionRequestsV2 is true, we expect an ArrayBuffer not empty
|
||||
}
|
||||
|
||||
// The attachment id is actually just the absolute url of the attachment
|
||||
let data = res;
|
||||
if (!attachment.isRaw) {
|
||||
|
|
|
@ -15,9 +15,9 @@ import { getSnodesFor } from '../snode_api/snodePool';
|
|||
import { PubKey } from '../types';
|
||||
import { actions as conversationActions } from '../../state/ducks/conversations';
|
||||
import { getV2OpenGroupRoom, removeV2OpenGroupRoom } from '../../data/opengroups';
|
||||
import { deleteAuthToken } from '../../opengroup/opengroupV2/OpenGroupAPIV2';
|
||||
import _ from 'lodash';
|
||||
import { OpenGroupManagerV2 } from '../../opengroup/opengroupV2/OpenGroupManagerV2';
|
||||
import { deleteAuthToken } from '../../opengroup/opengroupV2/ApiAuth';
|
||||
|
||||
export class ConversationController {
|
||||
private static instance: ConversationController | null;
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
QuotedAttachment,
|
||||
} from '../messages/outgoing/visibleMessage/VisibleMessage';
|
||||
import { OpenGroup } from '../../opengroup/opengroupV1/OpenGroup';
|
||||
import { FSv2 } from '../../fileserver';
|
||||
|
||||
interface UploadParams {
|
||||
attachment: Attachment;
|
||||
|
@ -55,6 +56,7 @@ export class AttachmentUtils {
|
|||
}
|
||||
|
||||
let server = window.tokenlessFileServerAdnAPI;
|
||||
// this can only be an opengroupv1
|
||||
if (openGroup) {
|
||||
const openGroupServer = await window.lokiPublicChatAPI.findOrCreateServer(openGroup.server);
|
||||
if (!openGroupServer) {
|
||||
|
@ -92,12 +94,23 @@ export class AttachmentUtils {
|
|||
attachmentData = data.ciphertext;
|
||||
}
|
||||
|
||||
const result = isAvatar
|
||||
? await server.putAvatar(attachmentData)
|
||||
: await server.putAttachment(attachmentData);
|
||||
// use file server v2
|
||||
|
||||
pointer.id = result.id;
|
||||
pointer.url = result.url;
|
||||
if (FSv2.useFileServerAPIV2Sending) {
|
||||
const uploadToV2Result = await FSv2.uploadFileToFsV2(attachmentData);
|
||||
if (uploadToV2Result) {
|
||||
pointer.id = uploadToV2Result.fileId;
|
||||
pointer.url = uploadToV2Result.fileUrl;
|
||||
} else {
|
||||
console.warn('upload to file server v2 failed');
|
||||
}
|
||||
} else {
|
||||
const result = isAvatar
|
||||
? await server.putAvatar(attachmentData)
|
||||
: await server.putAttachment(attachmentData);
|
||||
pointer.id = result.id;
|
||||
pointer.url = result.url;
|
||||
}
|
||||
|
||||
return pointer;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue