Files
iOS/Sources/Shared/API/Webhook/Request&Response/WebhookRequest.swift
Zac West 4d9a530637 Reorganize files in repo, pull out build settings from pbxproj (#1140)
This is somewhat in prep of being able to make the project file generated, but also just organizes things into more concrete directory structures.

This pulls out _all_ of the build settings from the root level, and most from the target level, into xcconfigs.

The new directory structure looks like:

- Sources
  - App
    - (everything from HomeAssistant/)
  - WatchApp
  - Shared
  - MacBridge
  - Extensions
    - Intents
    - NotificationContent
    - NotificationService
    - Share
    - Today
    - Watch
    - Widgets
- Tests
  - App
  - UI
  - Shared

Somewhat intentionally, the file structure under these is not yet standardized/organized.

The project targets are now:

- App
- WatchApp
- Shared-iOS
- Shared-watchOS
- MacBridge
- Tests-App
- Tests-UI
- Tests-Shared
- Extension-Intents
- Extension-NotificationContent
- Extension-NotificationService
- Extension-Share
- Extension-Today
- Extension-Widget
- WatchExtension-Watch

This does not yet clean up resources vs. sources, nor does it handle some of the "it's in Sources/App but it's part of Shared" crossover directory issues.
2020-10-03 00:15:04 -07: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
}
}