iOS/Sources/Shared/API/Webhook/Networking/WebhookRequest.swift

91 lines
2.6 KiB
Swift

import Foundation
import ObjectMapper
import Sodium
public enum WebhookRequestContext: MapContext, Equatable {
case server(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 case let .server(server) = context, let encrypted = encryptedData(server: server) {
true >>> map["encrypted"]
encrypted >>> map["encrypted_data"]
} else {
data >>> map["data"]
}
}
private func encryptedData(server: Server) -> String? {
guard let secret = server.info.connection.webhookSecretBytes(version: server.info.version) 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
}
guard let encryptedData: Bytes = sodium.secretBox.seal(
message: jsonStr.bytes,
secretKey: .init(secret)
) 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
}
}