diff --git a/.travis.yml b/.travis.yml index 531e6d8b1f..acf9381928 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,7 +34,7 @@ deploy: secret_access_key: $AWS_SECRET bucket: 'jbrowse.org' local_dir: products/jbrowse-web/build - upload_dir: code/jb2/beta/$TRAVIS_BRANCH + upload_dir: code/jb2/$TRAVIS_BRANCH on: repo: GMOD/jbrowse-components all_branches: true diff --git a/package.json b/package.json index 81c3e8c61e..d3267be392 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@types/base64-js": "^1.2.5", "@types/clone": "^0.1.30", "@types/color": "^3.0.1", + "@types/crypto-js": "^4.0.1", "@types/d3-scale": "^2.2.0", "@types/deep-equal": "^1.0.1", "@types/dompurify": "^2.0.2", diff --git a/products/jbrowse-lambda-functions/saved-sessions/save-session-jbrowse.js b/products/jbrowse-lambda-functions/saved-sessions/save-session-jbrowse.js index 22e17bc783..ff609f5d9a 100644 --- a/products/jbrowse-lambda-functions/saved-sessions/save-session-jbrowse.js +++ b/products/jbrowse-lambda-functions/saved-sessions/save-session-jbrowse.js @@ -7,8 +7,16 @@ const { AWS_REGION: region, sessionTable } = process.env const dynamodb = new AWS.DynamoDB({ apiVersion: '2012-08-10', region }) +// slice of session hash in base64, add a math.random so that same session maps +// to new id function generateSessionId(session) { - return createHash('sha256').update(session).digest('hex') + return createHash('sha256') + .update(session + Math.random()) + .digest('base64') + .slice(0, 10) + .replace('+', '-') + .replace('/', '_') + .replace(/=+$/, '') } async function uploadSession(sessionId, session, dateShared, referer) { diff --git a/products/jbrowse-web/package.json b/products/jbrowse-web/package.json index 1352bfe128..9d171d0fc2 100644 --- a/products/jbrowse-web/package.json +++ b/products/jbrowse-web/package.json @@ -45,6 +45,7 @@ "clsx": "^1.0.4", "copy-to-clipboard": "^3.3.1", "core-js": "^3.2.1", + "crypto-js": "^4.0.0", "fastestsmallesttextencoderdecoder": "^1.0.14", "fontsource-roboto": "3.0.3", "json-stable-stringify": "^1.0.1", diff --git a/products/jbrowse-web/src/Loader.tsx b/products/jbrowse-web/src/Loader.tsx index 9a3c7ac20e..05b3324c71 100644 --- a/products/jbrowse-web/src/Loader.tsx +++ b/products/jbrowse-web/src/Loader.tsx @@ -16,7 +16,6 @@ import { types, Instance, SnapshotOut } from 'mobx-state-tree' import { PluginConstructor } from '@jbrowse/core/Plugin' import { FatalErrorDialog } from '@jbrowse/core/ui' import { TextDecoder, TextEncoder } from 'fastestsmallesttextencoderdecoder' -import * as crypto from 'crypto' import 'fontsource-roboto' import 'requestidlecallback-polyfill' import 'core-js/stable' @@ -274,8 +273,6 @@ const SessionLoader = types }, async fetchSharedSession() { - const key = crypto.createHash('sha256').update('JBrowse').digest() - // raw readConf alternative for before conf is initialized const readConf = ( conf: { configuration?: { [key: string]: string } }, @@ -290,7 +287,6 @@ const SessionLoader = types const decryptedSession = await readSessionFromDynamo( `${readConf(self.configSnapshot, 'shareURL', defaultURL)}load`, self.sessionQuery || '', - key, self.password || '', ) diff --git a/products/jbrowse-web/src/ShareButton.tsx b/products/jbrowse-web/src/ShareButton.tsx index 1bc3f6579e..211c0b1a95 100644 --- a/products/jbrowse-web/src/ShareButton.tsx +++ b/products/jbrowse-web/src/ShareButton.tsx @@ -210,7 +210,7 @@ const ShareDialog = observer( setLoading(false) const params = new URLSearchParams(locationUrl.search) params.set('session', `share-${result.json.sessionId}`) - params.set('password', result.encryptedSession.iv) + params.set('password', result.password) locationUrl.search = params.toString() setShortUrl(locationUrl.href) } diff --git a/products/jbrowse-web/src/sessionSharing.ts b/products/jbrowse-web/src/sessionSharing.ts index 6bdea5b4f0..cd87637b3c 100644 --- a/products/jbrowse-web/src/sessionSharing.ts +++ b/products/jbrowse-web/src/sessionSharing.ts @@ -1,22 +1,27 @@ -import * as crypto from 'crypto' import { toUrlSafeB64 } from '@jbrowse/core/util' -// adapted encrypt from https://gist.github.com/vlucas/2bd40f62d20c1d49237a109d491974eb -const encrypt = (text: string, key: Buffer, iv: Buffer) => { - const cipher = crypto.createCipheriv('aes-256-cbc', Buffer.from(key), iv) - let encrypted = cipher.update(text) - encrypted = Buffer.concat([encrypted, cipher.final()]) - return { iv: iv.toString('hex'), encryptedData: encrypted.toString('hex') } +import AES from 'crypto-js/aes' +import Utf8 from 'crypto-js/enc-utf8' + +// from https://stackoverflow.com/questions/1349404/ +function generateUID(length: number) { + return window + .btoa( + Array.from(window.crypto.getRandomValues(new Uint8Array(length * 2))) + .map(b => String.fromCharCode(b)) + .join(''), + ) + .replace(/[+/]/g, '') + .substring(0, length) +} + +const encrypt = (text: string, password: string) => { + return AES.encrypt(text, password).toString() } -// adapted decrypt from https://gist.github.com/vlucas/2bd40f62d20c1d49237a109d491974eb -const decrypt = (text: string, key: Buffer, password: string) => { - const iv = Buffer.from(password, 'hex') - const encryptedText = Buffer.from(text, 'hex') - const decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv) - let decrypted = decipher.update(encryptedText) - decrypted = Buffer.concat([decrypted, decipher.final()]) - return decrypted.toString() +const decrypt = (text: string, password: string) => { + const bytes = AES.decrypt(text, password) + return bytes.toString(Utf8) } // writes the encrypted session, current datetime, and referer to DynamoDB export async function shareSessionToDynamo( @@ -25,12 +30,11 @@ export async function shareSessionToDynamo( referer: string, ) { const sess = `${toUrlSafeB64(JSON.stringify(session))}` - const key = crypto.createHash('sha256').update('JBrowse').digest() - const iv = crypto.randomBytes(16) - const encryptedSession = encrypt(sess, key, iv) + const password = generateUID(5) + const encryptedSession = encrypt(sess, password) const data = new FormData() - data.append('session', encryptedSession.encryptedData) + data.append('session', encryptedSession) data.append('dateShared', `${Date.now()}`) data.append('referer', referer) @@ -47,13 +51,13 @@ export async function shareSessionToDynamo( return { json, encryptedSession, + password, } } export async function readSessionFromDynamo( baseUrl: string, sessionQueryParam: string, - key: Buffer, password: string, signal?: AbortSignal, ) { @@ -77,5 +81,5 @@ export async function readSessionFromDynamo( throw new Error(`Unable to fetch session ${sessionId}`) } const json = JSON.parse(text) - return decrypt(json.session, key, password) + return decrypt(json.session, password) } diff --git a/products/jbrowse-web/src/tests/Loader.test.tsx b/products/jbrowse-web/src/tests/Loader.test.tsx index a74adf87a9..4f2eea8af3 100644 --- a/products/jbrowse-web/src/tests/Loader.test.tsx +++ b/products/jbrowse-web/src/tests/Loader.test.tsx @@ -22,12 +22,7 @@ const readBuffer = async (url: string, args: RequestInit) => { return { ok: true, async text() { - return ` - { - "session": - "62fbd7cfbbd56f5d6a6fcf118a954a42706fce7cd91b344cd22687ece0ffd4ff38ea78e6c01f85c487904a861943c30dbe6547437448f887dcea94f5b5e79e3342f28e173a19faf615016235bce6d001082836626d6dc5a18a7c494ca66d612f1c59441de2bcab86785afab450f03f79ac54c6f9fd69d1a1074d690f94c0c3f80aaf41f75bc84f8bca23e509ba083bb6b44d463cc2ce923f644cd39d343bbbbf2eb63108917d17ad474c6526acf7dc2634d27e10da283b2c0e5dfe0e408026d7d9ece64f1e61b2507df3ce78279985e70ee11068882225e2f41e2a376d185a6a192024bfa2a6a6456ceea99a654d27201fa7f2e379f4a99ca029613a82850349bde2e0edf947077889e37ae1912d2586a95bea6c268983c9d3b8da2ba03880804defc312af1b7c60109cda78c0453780f13258344d54df4578972966d415fb425fd943193e1665713d93fec066589ce0ae401581a54d33783503bd04cf7cf1610e5cea2354434878e69d9cad137bd2c97462f9cbbd8e7fd2daa9a05b35922bf8314296b3a0262c9c82a36c9858d24a7bd099416e5fcdac21a08fb302e158f7063a5f81aab9d5026f1ed76699c0a79a1f33fd44fb8e9849b0661d6306bc85ea0326062e24b473b6f91aa473fa97e43ed701092143fd3eb4208c97fef9c91fb6da6c70f3995df87ef93db05a958e73c60d2201ada77d110a3c6e8df1c856c29e37" - } - ` + return `{"session":"U2FsdGVkX1+9+Hsy+o75Cdyb1jGYB/N1/h6Jr5ARZRF02uH2AN70Uc/yTXAEo4PQMVypDZMLqO+LJcnF6k2FKfRo9w3oeL+EbWZsXgsTrP5IrE+xYN1wfdTKoIohbQMI+zcIZGLVNf7UqNZjwzsIracm5DkgZh9EWo4MAkBP10ZZEWSdV7gmg95a5ofta2bOMpL4T5yOdukBa+6Uvv9qYXt2KdZPR4PoVLQUTE67zIdc0A9n9BuXiTOFUmczfJVvkoQSOGaXGgSUVoK31Ei12lk67a55YtbG3ClENIMcSK/YbMH7w9HtqImzPY0jaQZSZ6ikKW8fXIbXmqX0oadOKS70RNVcF5JcDMYKx6zPxAf7WjpuFh+cNNr7j6bizRoTbuZi+xNsPpnA2QmbtOXCQzbOao1Oj3HzriBAIGC56bSxx0YfJ0en751LV6yrLPsnMmmmowTIjkbH5c+QRJId9sdYQb9Ytqr2dWBKixHSGhLBfdNr0yt3t5GQRu11Rlq6OekrA9KcmHv9QU3AhDtj9TYjG5vqveYCDfS7uSc3TJLEczwF8p02wjuGapYV5QpX+Lm9ADO8X+qW+bFZj3EGKoQBTUSfV1fd3t5oH3KWWuWYpMuRLbSYgcjKC29DOUJA43k+Ufmio+wO7CufcgGkIWlpejojX8f28UsPXaONmd3t8H4bmzXkB631E1EVS4y+RZGxc2uSVedS446qq/9tV9XJW9tkwNINwbpMHAG0OZk="}` }, } } @@ -120,7 +115,7 @@ describe('', () => { // @ts-ignore location={{ search: - '?config=test_data/volvox/config_main_thread.json&session=share-testid&password=17732efacf3f4909151acb11e1d5f057', + '?config=test_data/volvox/config_main_thread.json&session=share-testid&password=Z42aq', }} > diff --git a/yarn.lock b/yarn.lock index 1c4f31a5bb..28bf153b7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4627,6 +4627,11 @@ "@types/node" "*" "@types/webpack" "*" +"@types/crypto-js@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.0.1.tgz#3a4bd24518b0e6c5940da4e2659eeb2ef0806963" + integrity sha512-6+OPzqhKX/cx5xh+yO8Cqg3u3alrkhoxhE5ZOdSEv0DOzJ13lwJ6laqGU0Kv6+XDMFmlnGId04LtY22PsFLQUw== + "@types/d3-scale@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-2.2.0.tgz#e5987a2857365823eb26ed5eb21bc566c4dcf1c0" @@ -8061,6 +8066,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-js@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.0.0.tgz#2904ab2677a9d042856a2ea2ef80de92e4a36dcc" + integrity sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg== + crypto-random-string@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"