1import logger from '../logger' 2import * as Types from '../constants/types/crypto' 3import * as Constants from '../constants/crypto' 4import * as Container from '../util/container' 5import * as TeamBuildingGen from '../actions/team-building-gen' 6import * as CryptoGen from '../actions/crypto-gen' 7import HiddenString from '../util/hidden-string' 8import {editTeambuildingDraft} from './team-building' 9import {teamBuilderReducerCreator} from '../team-building/reducer-helper' 10 11const initialState: Types.State = Constants.makeState() 12 13type Actions = CryptoGen.Actions | TeamBuildingGen.Actions 14 15const operationGuard = (operation: Types.Operations, action: CryptoGen.Actions) => { 16 if (operation) return false 17 18 logger.error( 19 `Crypto reducer: Action (${action.type}) did not contain operation ( "encrypt", "decrypt", "verify", "sign" )` 20 ) 21 return true 22} 23 24const resetOutput = (op: Types.CommonState) => { 25 op.output = new HiddenString('') 26 op.outputStatus = undefined 27 op.outputType = undefined 28 op.outputSenderUsername = undefined 29 op.outputSenderFullname = undefined 30 op.outputValid = false 31 op.errorMessage = new HiddenString('') 32 op.warningMessage = new HiddenString('') 33} 34 35export default Container.makeReducer<Actions, Types.State>(initialState, { 36 [CryptoGen.resetStore]: () => { 37 return initialState 38 }, 39 [CryptoGen.resetOperation]: (draftState, action) => { 40 const {operation} = action.payload 41 42 if (operationGuard(operation, action)) return 43 44 switch (operation) { 45 case Constants.Operations.Encrypt: { 46 draftState.encrypt = initialState.encrypt 47 break 48 } 49 case Constants.Operations.Decrypt: { 50 draftState.decrypt = initialState.decrypt 51 break 52 } 53 case Constants.Operations.Sign: { 54 draftState.sign = initialState.sign 55 break 56 } 57 case Constants.Operations.Verify: { 58 draftState.verify = initialState.verify 59 break 60 } 61 } 62 }, 63 [CryptoGen.clearInput]: (draftState, action) => { 64 const {operation} = action.payload 65 66 if (operationGuard(operation, action)) return 67 68 const op = draftState[operation] 69 op.bytesComplete = 0 70 op.bytesTotal = 0 71 op.inputType = 'text' 72 op.input = new HiddenString('') 73 op.output = new HiddenString('') 74 op.outputStatus = undefined 75 op.outputType = undefined 76 op.outputSenderUsername = undefined 77 op.outputSenderFullname = undefined 78 op.errorMessage = new HiddenString('') 79 op.warningMessage = new HiddenString('') 80 op.outputValid = true 81 }, 82 [CryptoGen.clearRecipients]: (draftState, action) => { 83 const {operation} = action.payload 84 85 if (operationGuard(operation, action)) return 86 87 if (operation === Constants.Operations.Encrypt) { 88 const encrypt = draftState.encrypt 89 encrypt.bytesComplete = 0 90 encrypt.bytesTotal = 0 91 encrypt.recipients = initialState.encrypt.recipients 92 // Reset options since they depend on the recipients 93 encrypt.options = initialState.encrypt.options 94 encrypt.meta = initialState.encrypt.meta 95 encrypt.output = new HiddenString('') 96 encrypt.outputStatus = undefined 97 encrypt.outputType = undefined 98 encrypt.outputSenderUsername = undefined 99 encrypt.outputSenderFullname = undefined 100 encrypt.outputValid = false 101 encrypt.errorMessage = new HiddenString('') 102 encrypt.warningMessage = new HiddenString('') 103 } 104 }, 105 [CryptoGen.setRecipients]: (draftState, action) => { 106 const {operation, recipients, hasSBS} = action.payload 107 108 if (operationGuard(operation, action)) return 109 110 if (operation !== Constants.Operations.Encrypt) return 111 112 const op = draftState.encrypt 113 const {inputType} = op 114 115 // Reset output when file input changes 116 // Prompt for destination dir 117 if (inputType === 'file') { 118 resetOutput(op) 119 } 120 121 // Output no longer valid since recipients have changed 122 op.outputValid = false 123 124 if (!op.recipients.length && recipients.length) { 125 op.meta.hasRecipients = true 126 op.meta.hasSBS = hasSBS 127 } 128 // Force signing when user is SBS 129 if (hasSBS) { 130 op.options.sign = true 131 } 132 133 if (recipients) { 134 op.recipients = recipients 135 } 136 }, 137 [CryptoGen.setEncryptOptions]: (draftState, action) => { 138 const {options: newOptions, hideIncludeSelf} = action.payload 139 const {encrypt} = draftState 140 const {inputType} = encrypt 141 const oldOptions = encrypt.options 142 encrypt.options = { 143 ...oldOptions, 144 ...newOptions, 145 } 146 147 // Reset output when file input changes 148 // Prompt for destination dir 149 if (inputType === 'file') { 150 resetOutput(encrypt) 151 } 152 153 // Output no longer valid since options have changed 154 encrypt.outputValid = false 155 156 // User set themselves as a recipient so don't show the 'includeSelf' option for encrypt (since they're encrypting to themselves) 157 if (hideIncludeSelf) { 158 encrypt.meta.hideIncludeSelf = hideIncludeSelf 159 encrypt.options.includeSelf = false 160 } 161 }, 162 [CryptoGen.setInput]: (draftState, action) => { 163 const {operation, type, value} = action.payload 164 if (operationGuard(operation, action)) return 165 166 const op = draftState[operation] 167 const oldInput = op.input 168 // Reset input to 'text' when no value given (cleared input or removed file upload) 169 const inputType = value.stringValue() ? type : 'text' 170 const outputValid = oldInput.stringValue() === value.stringValue() 171 172 op.inputType = inputType 173 op.input = value 174 op.outputValid = outputValid 175 op.errorMessage = new HiddenString('') 176 op.warningMessage = new HiddenString('') 177 178 // Reset output when file input changes 179 // Prompt for destination dir 180 if (inputType === 'file') { 181 resetOutput(op) 182 } 183 }, 184 [CryptoGen.runFileOperation]: (draftState, action) => { 185 const {operation} = action.payload 186 if (operationGuard(operation, action)) return 187 188 const op = draftState[operation] 189 op.outputValid = false 190 op.errorMessage = new HiddenString('') 191 op.warningMessage = new HiddenString('') 192 }, 193 [CryptoGen.saltpackDone]: (draftState, action) => { 194 const {operation} = action.payload 195 const op = draftState[operation] 196 // For any file operation that completes, invalidate the output since multiple decrypt/verify operations will produce filenames with unqiue 197 // counters on the end (as to not overwrite any existing files in the user's FS). 198 // E.g. `${plaintextFilename} (n).ext` 199 op.outputValid = false 200 op.bytesComplete = 0 201 op.bytesTotal = 0 202 op.inProgress = false 203 op.outputStatus = 'pending' 204 }, 205 [CryptoGen.onSaltpackOpenFile]: (draftState, action) => { 206 const {operation, path} = action.payload 207 const op = draftState[operation] 208 const {inProgress} = op 209 210 // Bail on setting operation input if another file RPC is in progress 211 if (inProgress) return 212 if (!path.stringValue()) return 213 214 resetOutput(op) 215 op.input = path 216 op.inputType = 'file' 217 op.errorMessage = new HiddenString('') 218 op.warningMessage = new HiddenString('') 219 }, 220 [CryptoGen.onOperationSuccess]: (draftState, action) => { 221 const { 222 input, 223 operation, 224 output, 225 outputSigned, 226 outputSenderFullname, 227 outputSenderUsername, 228 outputType, 229 warning, 230 warningMessage, 231 } = action.payload 232 if (operationGuard(operation, action)) return 233 234 let inputAction: 235 | CryptoGen.SaltpackDecryptPayload 236 | CryptoGen.SaltpackEncryptPayload 237 | CryptoGen.SaltpackSignPayload 238 | CryptoGen.SaltpackVerifyPayload 239 | undefined 240 241 switch (input?.type) { 242 // fallthrough 243 case CryptoGen.saltpackDecrypt: 244 case CryptoGen.saltpackEncrypt: 245 case CryptoGen.saltpackSign: 246 case CryptoGen.saltpackVerify: 247 inputAction = input 248 break 249 default: 250 inputAction = undefined 251 } 252 253 let outputValid = false 254 255 const op = draftState[operation] 256 257 if (inputAction) { 258 outputValid = inputAction.payload.input.stringValue() === op.input.stringValue() 259 260 // If the store's input matches its output, then we don't need to update with the value of the returning RPC. 261 if (op.outputValid) { 262 return 263 } 264 265 // Otherwise show the output but don't let them interact with it because the output is stale (newer RPC coming back) 266 op.outputValid = outputValid 267 } 268 269 // Reset errors and warnings 270 op.errorMessage = new HiddenString('') 271 op.warningMessage = new HiddenString('') 272 273 // Warning was set alongside successful output 274 if (warning && warningMessage) { 275 op.warningMessage = warningMessage 276 } 277 278 op.output = output 279 op.outputStatus = 'success' 280 op.outputType = outputType 281 op.outputSigned = outputSigned 282 op.outputSenderUsername = outputSenderUsername 283 op.outputSenderFullname = outputSenderFullname 284 }, 285 [CryptoGen.onOperationError]: (draftState, action) => { 286 const {operation, errorMessage} = action.payload 287 if (operationGuard(operation, action)) return 288 289 const op = draftState[operation] 290 // Clear output 291 op.output = new HiddenString('') 292 op.outputType = undefined 293 294 // Set error 295 op.outputStatus = 'error' 296 op.errorMessage = errorMessage 297 }, 298 [CryptoGen.saltpackStart]: (draftState, action) => { 299 const {operation} = action.payload 300 if (operationGuard(operation, action)) return 301 302 // Gets the progress bar on screen sooner. This matters most when encrypting/signing a directory (since progress is slow) 303 const op = draftState[operation] 304 op.inProgress = true 305 }, 306 [CryptoGen.saltpackProgress]: (draftState, action) => { 307 const {bytesComplete, bytesTotal, operation} = action.payload 308 if (operationGuard(operation, action)) return 309 310 const op = draftState[operation] 311 // The final progress notification might come after saltpackDone. 312 // Reset progress when finsihed 313 if (bytesComplete === bytesTotal) { 314 op.bytesComplete = 0 315 op.bytesTotal = 0 316 op.inProgress = false 317 op.outputStatus = 'pending' 318 return 319 } 320 op.bytesComplete = bytesComplete 321 op.bytesTotal = bytesTotal 322 op.inProgress = true 323 }, 324 325 // Encrypt: Handle team building when selecting keybase users 326 ...teamBuilderReducerCreator<Types.State>( 327 (draftState: Container.Draft<Types.State>, action: TeamBuildingGen.Actions) => { 328 const val = editTeambuildingDraft('crypto', draftState.teamBuilding, action) 329 if (val !== undefined) { 330 draftState.teamBuilding = val 331 } 332 } 333 ), 334}) 335