diff --git a/lib/utils/sanitization.js b/lib/utils/sanitization.js index 304a9369c..c3842c1ae 100644 --- a/lib/utils/sanitization.js +++ b/lib/utils/sanitization.js @@ -1,310 +1,310 @@ // @flow import clone from 'just-clone'; import stringHash from 'string-hash'; import { setDeviceTokenActionTypes } from '../actions/device-actions'; import type { BaseAction, NativeAppState, AppState, } from '../types/redux-types'; import { setNewSessionActionType } from './action-utils'; export type ReduxCrashReport = { +preloadedState: AppState, +currentState: AppState, +actions: $ReadOnlyArray, }; export type RedactionHelpers = { +redactString: string => string, +redactColor: string => string, }; // eg {"email":"squad@bot.com"} => {"email":"[redacted]"} const keysWithStringsToBeRedacted = new Set([ 'source', 'value', 'targetID', 'sourceMessageAuthorID', 'cookie', 'creatorID', 'currentMediaID', 'currentUser', 'childThreadID', 'dbText', 'description', 'draft', 'email', 'entryID', 'filename', 'first255Chars', 'id', 'inputFilename', 'latestMessage', 'localID', 'mediaLocalID', 'mediaNativeID', 'messageID', 'messageLocalID', 'messageServerID', 'name', 'newThreadID', 'parentThreadID', 'path', 'serverID', 'sessionID', 'sourceMessageID', 'threadID', 'thumbnailID', 'uploadLocalID', 'uploadServerID', 'uiName', 'username', 'deletedUserID', 'deviceToken', 'updatedUserID', 'role', ]); // eg {"memberIDs":["123", "456"]} => {"memberIDs":["redacted", "redacted"]} const keysWithArraysToBeRedacted = new Set([ 'memberIDs', 'messageIDs', 'already_friends', 'invalid_user', 'user_blocked', 'deletedEntryIDs', 'addedUserIDs', ]); // eg "userInfos":{"1":[Object]} => "userInfos":{"redacted":[Object]} const keysWithObjectsWithKeysToBeRedacted = new Set([ 'userInfos', 'threadInfos', 'threads', 'messages', 'entryInfos', 'roles', ]); // eg {"text":"hello world"} => {"text":"z6lgz waac5"} const keysWithStringsToBeScrambled = new Set(['text', 'robotext']); // eg {"uri":"https://comm.app/1234/5678"} -// => {"uri":"https://comm.app/images/background.png"} +// => {"uri":"https://comm.app/images/placeholder.png"} const keysWithImageURIsToBeReplaced = new Set([ 'uri', 'localURI', 'inputURI', 'outputURI', 'newURI', 'thumbnailURI', ]); // (special case that redacts triply-linked [] to handle `daysToEntries` ) // eg "daysToEntries":{"2020-12-29":["123"]} // => "daysToEntries":{"2020-12-29":["redacted"]} const keysWithObjectsWithArraysToBeRedacted = new Set(['daysToEntries']); function generateSaltedRedactionFn(): string => string { const salt = Math.random().toString(36); return (str: string) => { return `[redacted-${stringHash(str.concat(salt))}]`; }; } function generateColorRedactionFn(): string => string { const salt = Math.random().toString(16); return (oldColor: string) => { return `${stringHash(oldColor.concat(salt)).toString(16).slice(0, 6)}`; }; } function placeholderImageURI(): string { - return 'https://comm.app/images/background.png'; + return 'https://comm.app/images/placeholder.png'; } function scrambleText(str: string): string { const arr = []; for (const char of str) { if (char === ' ') { arr.push(' '); continue; } const randomChar = Math.random().toString(36)[2]; arr.push(randomChar); } return arr.join(''); } function sanitizeReduxReport(reduxReport: ReduxCrashReport): ReduxCrashReport { const redactionHelpers: RedactionHelpers = { redactString: generateSaltedRedactionFn(), redactColor: generateColorRedactionFn(), }; return { preloadedState: sanitizeState(reduxReport.preloadedState, redactionHelpers), currentState: sanitizeState(reduxReport.currentState, redactionHelpers), actions: reduxReport.actions.map(x => sanitizeAction(sanitizeActionSecrets(x), redactionHelpers), ), }; } const MessageListRouteName = 'MessageList'; const ThreadSettingsRouteName = 'ThreadSettings'; function potentiallyRedactReactNavigationKey( key: string, redactionFn: string => string, ): string { if (key.startsWith(MessageListRouteName)) { return `${MessageListRouteName}${redactionFn( key.substring(MessageListRouteName.length), )}`; } else if (key.startsWith(ThreadSettingsRouteName)) { return `${ThreadSettingsRouteName}${redactionFn( key.substring(ThreadSettingsRouteName.length), )}`; } return key; } function sanitizeNavState( obj: Object, redactionHelpers: RedactionHelpers, ): void { for (const k in obj) { if (k === 'params') { sanitizePII(obj[k], redactionHelpers); } else if (k === 'key') { obj[k] = potentiallyRedactReactNavigationKey( obj[k], redactionHelpers.redactString, ); } else if (typeof obj[k] === 'object') { sanitizeNavState(obj[k], redactionHelpers); } } } function sanitizePII(obj: Object, redactionHelpers: RedactionHelpers): void { for (const k in obj) { if (k === 'navState') { sanitizeNavState(obj[k], redactionHelpers); continue; } if (keysWithObjectsWithKeysToBeRedacted.has(k)) { for (const keyToBeRedacted in obj[k]) { obj[k][redactionHelpers.redactString(keyToBeRedacted)] = obj[k][keyToBeRedacted]; delete obj[k][keyToBeRedacted]; } } if (keysWithObjectsWithArraysToBeRedacted.has(k)) { for (const arrayToBeRedacted in obj[k]) { obj[k][arrayToBeRedacted] = obj[k][arrayToBeRedacted].map( redactionHelpers.redactString, ); } } if (keysWithStringsToBeRedacted.has(k) && typeof obj[k] === 'string') { obj[k] = redactionHelpers.redactString(obj[k]); } else if (k === 'key') { obj[k] = potentiallyRedactReactNavigationKey( obj[k], redactionHelpers.redactString, ); } else if (k === 'color') { obj[k] = redactionHelpers.redactColor(obj[k]); } else if (keysWithStringsToBeScrambled.has(k)) { obj[k] = scrambleText(obj[k]); } else if (keysWithImageURIsToBeReplaced.has(k)) { obj[k] = placeholderImageURI(); } else if (keysWithArraysToBeRedacted.has(k)) { obj[k] = obj[k].map(redactionHelpers.redactString); } else if (typeof obj[k] === 'object') { sanitizePII(obj[k], redactionHelpers); } } } function sanitizeActionSecrets(action: BaseAction): BaseAction { if (action.type === setNewSessionActionType) { const { sessionChange } = action.payload; if (sessionChange.cookieInvalidated) { const { cookie, ...rest } = sessionChange; return { type: 'SET_NEW_SESSION', payload: { ...action.payload, sessionChange: { cookieInvalidated: true, ...rest }, }, }; } else { const { cookie, ...rest } = sessionChange; return { type: 'SET_NEW_SESSION', payload: { ...action.payload, sessionChange: { cookieInvalidated: false, ...rest }, }, }; } } else if ( action.type === setDeviceTokenActionTypes.started && action.payload ) { return ({ type: 'SET_DEVICE_TOKEN_STARTED', payload: 'FAKE', loadingInfo: action.loadingInfo, }: any); } else if (action.type === setDeviceTokenActionTypes.success) { return { type: 'SET_DEVICE_TOKEN_SUCCESS', payload: 'FAKE', loadingInfo: action.loadingInfo, }; } return action; } function sanitizeAction( action: BaseAction, redactionHelpers: RedactionHelpers, ): BaseAction { const actionCopy = clone(action); sanitizePII(actionCopy, redactionHelpers); return actionCopy; } function sanitizeState( state: AppState, redactionHelpers: RedactionHelpers, ): AppState { if (state.cookie !== undefined && state.cookie !== null) { const oldState: NativeAppState = state; state = { ...oldState, cookie: null }; } if (state.deviceToken !== undefined && state.deviceToken !== null) { const oldState: NativeAppState = state; state = { ...oldState, deviceToken: null }; } const stateCopy = clone(state); sanitizePII(stateCopy, redactionHelpers); return stateCopy; } export { sanitizeActionSecrets, sanitizeAction, sanitizeState, sanitizeReduxReport, }; diff --git a/server/images/background@2x.png b/server/images/background@2x.png deleted file mode 100644 index 22441527c..000000000 Binary files a/server/images/background@2x.png and /dev/null differ diff --git a/server/images/background.png b/server/images/placeholder.png similarity index 100% rename from server/images/background.png rename to server/images/placeholder.png diff --git a/web/splash/splash.css b/web/splash/splash.css index eaa5669ef..ef3ac43ae 100644 --- a/web/splash/splash.css +++ b/web/splash/splash.css @@ -1,217 +1,211 @@ div.headerContainer { flex: 1 1 auto; - background-image: url(../images/background.png); - background-size: 3000px 2000px; - background-attachment: fixed; padding: 0 10px; } div.topContainer { position: absolute; top: 0; left: 0; right: 0; } div.content { flex: 1 0 0; position: relative; min-height: 100%; } div.top { max-width: 1024px; margin: 0 auto; } div.headerContents { display: flex; justify-content: space-between; align-items: center; padding-top: 5px; padding-bottom: 7px; } div.actionLinks > * { padding-right: 15px; color: white; font-weight: 300; } div.actionLinks > *:last-child { padding-right: 0; } header.header h1 { color: white; font-size: 38px; } header.header span.requestAccess { padding: 10px 20px; border-radius: 20px; border: 1px solid white; } div.body { padding: 30px 0 20px 0; } div.intro { text-align: center; margin-bottom: 50px; } p.introHeader { color: white; font-size: 28px; font-weight: 300; } p.introDescription { color: white; font-size: 21px; font-weight: 300; padding-top: 5px; } div.bottomContainer { z-index: 2; position: relative; overflow: auto; height: 100%; } div.bottom { margin-top: 728px; - background-image: url(../images/background.png); - background-size: 3000px 2000px; - background-attachment: fixed; min-height: 100%; display: flex; flex-direction: column; } div.headerRest { background-color: rgba(255, 255, 255, 0.84); flex-grow: 1; } div.headerOverscroll { position: fixed; top: 100%; left: 0; right: 0; height: 1000px; background-color: white; } div.prompt { margin: 0 auto; display: flex; flex-direction: column; align-items: center; } p.promptHeader { color: #282828; font-size: 21px; padding-top: 25px; text-align: center; } p.promptDescription { color: #282828; font-size: 18px; padding-top: 9px; text-align: center; } div.requestAccessContainer { margin: 25px; display: flex; flex-direction: column; align-items: center; } form.requestAccessForm { display: flex; align-items: center; } input.requestAccessEmail { width: 300px; border: 0; padding: 10px 10px 8px 10px; font-size: 18px; border-radius: 5px; background-color: rgba(119, 119, 119, 0.13); color: black; } input.requestAccessEmail::placeholder, input.requestAccessEmail::-ms-input-placeholder { color: #777777; } button.requestAccessSubmit { border: 0px; padding: 10px 10px 8px 10px; font-size: 18px; border-radius: 5px; background-color: rgba(0, 170, 0, 0.53); color: white; margin-left: 15px; cursor: pointer; width: 78px; height: 39px; } p.androidWarning { font-size: 14px; padding: 5px; } p.requestAccessError { font-size: 14px; padding: 10px; text-align: center; color: red; } p.requestAccessSuccess { font-size: 18px; padding: 20px; text-align: center; } div.customSelect { position: relative; display: block; border: 1px solid #bbb; border-radius: 0.3em; background-color: #f5f5f5; margin-left: 15px; } div.customSelect::after { content: ' '; position: absolute; top: 50%; right: 1em; z-index: 2; pointer-events: none; display: none; width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 7px solid #666; margin-top: -3px; } div.customSelect select { width: 100%; margin: 0; outline: none; padding: 0.6em 0.8em 0.5em 0.8em; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; font-size: 16px; font-family: sans-serif; color: #444; border-radius: 0.2em; } div.customSelect select:focus { color: #222; outline: none; } @supports (-webkit-appearance: none) or (appearance: none) or ((-moz-appearance: none) and (mask-type: alpha)) { div.customSelect::after { display: block; } div.customSelect select { padding-right: 2em; background: none; border: 1px solid transparent; -webkit-appearance: none; -moz-appearance: none; appearance: none; } div.customSelect select:focus { border-color: #aaa; } }