import { getV2OpenGroupRoomByRoomId, saveV2OpenGroupRoom } from '../../../../data/opengroups'; import { callUtilsWorker } from '../../../../webworker/workers/util_worker_interface'; import { allowOnlyOneAtATime } from '../../../utils/Promise'; import { toHex } from '../../../utils/String'; import { getIdentityKeyPair, getOurPubKeyStrFromCache } from '../../../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 { // 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; } async function oneAtATimeGetAuth({ serverUrl, roomId }: OpenGroupRequestCommonType) { return allowOnlyOneAtATime(`getAuthToken${serverUrl}:${roomId}`, async () => { try { // 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; } window?.log?.info( `Triggering getAuthToken with serverUrl:'${serverUrl}'; roomId: '${roomId}'` ); const token = await requestNewAuthToken({ serverUrl, roomId }); if (!token) { window?.log?.warn('invalid new auth token', token); return; } window?.log?.info(`Got AuthToken for serverUrl:'${serverUrl}'; roomId: '${roomId}'`); const claimedToken = await claimAuthToken(token, serverUrl, roomId); if (!claimedToken) { window?.log?.warn('Failed to claim token', claimedToken); } else { window?.log?.info(`Claimed AuthToken for serverUrl:'${serverUrl}'; roomId: '${roomId}'`); } // still save it to the db. just to mark it as to be refreshed later roomDetails.token = claimedToken || ''; await saveV2OpenGroupRoom(roomDetails); window?.log?.info(`AuthToken saved to DB for serverUrl:'${serverUrl}'; roomId: '${roomId}'`); return claimedToken; } catch (e) { window?.log?.error('Failed to getAuthToken', e); throw e; } }); } export async function getAuthToken({ serverUrl, roomId, }: OpenGroupRequestCommonType): Promise { return oneAtATimeGetAuth({ roomId, serverUrl }); } // tslint:disable: member-ordering export async function requestNewAuthToken({ serverUrl, roomId, }: OpenGroupRequestCommonType): Promise { const userKeyPair = await getIdentityKeyPair(); if (!userKeyPair) { throw new Error('Failed to fetch user keypair'); } const ourPubkey = getOurPubKeyStrFromCache(); const parameters = {} as Record; 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 = (await callUtilsWorker( 'fromBase64ToArrayBuffer', base64EncodedCiphertext )) as ArrayBuffer; const ephemeralPublicKey = (await callUtilsWorker( 'fromBase64ToArrayBuffer', base64EncodedEphemeralPublicKey )) as ArrayBuffer; try { const symmetricKey = (await callUtilsWorker( 'deriveSymmetricKey', new Uint8Array(ephemeralPublicKey), new Uint8Array(userKeyPair.privKey) )) as ArrayBuffer; const plaintextBuffer = await callUtilsWorker( 'DecryptAESGCM', new Uint8Array(symmetricKey), new Uint8Array(ciphertext) ); const token = toHex(plaintextBuffer); return token; } catch (e) { window?.log?.error('Failed to decrypt token open group v2'); return null; } }