Files
iOS/Sources/Shared/API/Webhook/Networking/WebhookRequest.swift
Zac West 640cca884b Add SwiftFormat to project (#1463)
Adds new fastlane lanes:
- `fastlane lint` which checks the linters
- `fastlane autocorrect` which applies the linters which can autocorrect (Rubocop, SwiftFormat)

Adds a build step to the Codegen abstract target which runs SwiftFormat in lint mode, pointing out what it's going to change when run.

Applies SwiftFormat to nearly all code -- exempts a few externally-sourced files and generated code.
2021-02-05 22:06:25 -08:00

93 lines
2.6 KiB
Swift

import Foundation
import ObjectMapper
import Sodium
public enum WebhookRequestContext: MapContext, Equatable {
case server
case local
}
public struct WebhookRequest: ImmutableMappable {
public let type: String
public let data: Any
public let localMetadata: [String: Any]?
public init(type: String, data: Any, localMetadata: [String: Any]? = nil) {
self.type = type
self.data = data
self.localMetadata = localMetadata
}
public init(map: Map) throws {
self.type = try map.value("type")
self.data = try map.value("data")
self.localMetadata = try? map.value("local_metadata")
}
enum ConversionError: Error {
case dictionary
}
func asDictionary() throws -> [String: Any] {
if let data = data as? [String: Any] {
return data
} else {
throw ConversionError.dictionary
}
}
public func mapping(map: Map) {
guard let context = map.context as? WebhookRequestContext else {
fatalError("context must be provided to avoid accidental unencrypted traffic")
}
type >>> map["type"]
if context == .local {
localMetadata >>> map["local_metadata"]
}
if context == .server, let encrypted = encryptedData() {
true >>> map["encrypted"]
encrypted >>> map["encrypted_data"]
} else {
data >>> map["data"]
}
}
private func encryptedData() -> String? {
guard let secret = Current.settingsStore.connectionInfo?.webhookSecret else {
return nil
}
let sodium = Sodium()
guard let jsonData = try? JSONSerialization.data(withJSONObject: data, options: [.sortedKeys]) else {
Current.Log.error("Unable to convert JSON dictionary to data!")
return nil
}
guard let jsonStr = String(data: jsonData, encoding: .utf8) else {
Current.Log.error("Unable to convert JSON data to string!")
return nil
}
let key: Bytes = Array(secret.bytes[0 ..< sodium.secretBox.KeyBytes])
guard let encryptedData: Bytes = sodium.secretBox.seal(
message: jsonStr.bytes,
secretKey: key
) else {
Current.Log.error("Unable to generate encrypted webhook payload! Secret: \(secret), JSON: \(jsonStr)")
return nil
}
guard let b64payload = sodium.utils.bin2base64(encryptedData, variant: .ORIGINAL) else {
Current.Log.error("Unable to encode encrypted payload to base64!")
return nil
}
return b64payload
}
}