wip
This commit is contained in:
parent
5f5e20b462
commit
d0cbe63eb1
|
@ -51,7 +51,8 @@ final class ClientMartinChatsManager: Martin.ChatManager {
|
||||||
id: UUID().uuidString,
|
id: UUID().uuidString,
|
||||||
account: context.userBareJid.stringValue,
|
account: context.userBareJid.stringValue,
|
||||||
participant: with.stringValue,
|
participant: with.stringValue,
|
||||||
type: .chat
|
type: .chat,
|
||||||
|
encrypted: UserSettings.secureChatsByDefault
|
||||||
)
|
)
|
||||||
try Database.shared.dbQueue.write { db in
|
try Database.shared.dbQueue.write { db in
|
||||||
try chat.save(db)
|
try chat.save(db)
|
||||||
|
|
|
@ -117,28 +117,34 @@ extension Client {
|
||||||
|
|
||||||
var msg = chat.createMessage(text: message.body ?? "??", id: message.id)
|
var msg = chat.createMessage(text: message.body ?? "??", id: message.id)
|
||||||
msg.oob = message.oobUrl
|
msg.oob = message.oobUrl
|
||||||
msg = try await encryptMessage(msg)
|
if message.secure {
|
||||||
|
msg = try await encryptMessage(msg)
|
||||||
|
}
|
||||||
try await chat.send(message: msg)
|
try await chat.send(message: msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func uploadFile(_ localURL: URL) async throws -> String {
|
func uploadFile(_ localURL: URL, needEncrypt: Bool) async throws -> String {
|
||||||
// get data from file
|
// get data from file
|
||||||
guard var data = try? Data(contentsOf: localURL) else {
|
guard var data = try? Data(contentsOf: localURL) else {
|
||||||
throw AppError.noData
|
throw AppError.noData
|
||||||
}
|
}
|
||||||
|
|
||||||
// encrypt data if needed
|
// encrypt data if needed
|
||||||
let key = try AESGSMEngine.generateKey()
|
var key = Data()
|
||||||
let iv = try AESGSMEngine.generateIV()
|
var iv = Data()
|
||||||
var encrypted = Data()
|
if needEncrypt {
|
||||||
var tag = Data()
|
key = try AESGSMEngine.generateKey()
|
||||||
guard AESGSMEngine.shared.encrypt(iv: iv, key: key, message: data, output: &encrypted, tag: &tag) else {
|
iv = try AESGSMEngine.generateIV()
|
||||||
throw AppError.securityError
|
var encrypted = Data()
|
||||||
}
|
var tag = Data()
|
||||||
|
guard AESGSMEngine.shared.encrypt(iv: iv, key: key, message: data, output: &encrypted, tag: &tag) else {
|
||||||
|
throw AppError.securityError
|
||||||
|
}
|
||||||
|
|
||||||
// attach tag to end of encrypted data
|
// attach tag to end of encrypted data
|
||||||
encrypted.append(tag)
|
encrypted.append(tag)
|
||||||
data = encrypted
|
data = encrypted
|
||||||
|
}
|
||||||
|
|
||||||
// upload
|
// upload
|
||||||
let httpModule = connection.module(HttpFileUploadModule.self)
|
let httpModule = connection.module(HttpFileUploadModule.self)
|
||||||
|
@ -164,15 +170,19 @@ extension Client {
|
||||||
let (_, response) = try await URLSession.shared.data(for: request)
|
let (_, response) = try await URLSession.shared.data(for: request)
|
||||||
switch response {
|
switch response {
|
||||||
case let httpResponse as HTTPURLResponse where httpResponse.statusCode == 201:
|
case let httpResponse as HTTPURLResponse where httpResponse.statusCode == 201:
|
||||||
guard var parts = URLComponents(url: slot.getUri, resolvingAgainstBaseURL: true) else {
|
if needEncrypt {
|
||||||
throw URLError(.badServerResponse)
|
guard var parts = URLComponents(url: slot.getUri, resolvingAgainstBaseURL: true) else {
|
||||||
|
throw URLError(.badServerResponse)
|
||||||
|
}
|
||||||
|
parts.scheme = "aesgcm"
|
||||||
|
parts.fragment = (iv + key).map { String(format: "%02x", $0) }.joined()
|
||||||
|
guard let shareUrl = parts.url else {
|
||||||
|
throw URLError(.badServerResponse)
|
||||||
|
}
|
||||||
|
return shareUrl.absoluteString
|
||||||
|
} else {
|
||||||
|
return slot.getUri.absoluteString
|
||||||
}
|
}
|
||||||
parts.scheme = "aesgcm"
|
|
||||||
parts.fragment = (iv + key).map { String(format: "%02x", $0) }.joined()
|
|
||||||
guard let shareUrl = parts.url else {
|
|
||||||
throw URLError(.badServerResponse)
|
|
||||||
}
|
|
||||||
return shareUrl.absoluteString
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw URLError(.badServerResponse)
|
throw URLError(.badServerResponse)
|
||||||
|
|
|
@ -14,6 +14,7 @@ struct Chat: DBStorable {
|
||||||
var account: String
|
var account: String
|
||||||
var participant: String
|
var participant: String
|
||||||
var type: ConversationType
|
var type: ConversationType
|
||||||
|
var encrypted: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Chat: Equatable {}
|
extension Chat: Equatable {}
|
||||||
|
|
|
@ -50,7 +50,7 @@ extension Message {
|
||||||
secure = true
|
secure = true
|
||||||
|
|
||||||
case .successTransportKey:
|
case .successTransportKey:
|
||||||
break
|
return nil
|
||||||
|
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
logIt(.error, "Error decoding omemo message: \(error)")
|
logIt(.error, "Error decoding omemo message: \(error)")
|
||||||
|
|
|
@ -97,6 +97,10 @@ extension Database {
|
||||||
try db.alter(table: "messages") { table in
|
try db.alter(table: "messages") { table in
|
||||||
table.add(column: "secure", .boolean).notNull().defaults(to: false)
|
table.add(column: "secure", .boolean).notNull().defaults(to: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try db.alter(table: "chats") { table in
|
||||||
|
table.add(column: "encrypted", .boolean).notNull().defaults(to: false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// return migrator
|
// return migrator
|
||||||
|
|
|
@ -12,8 +12,10 @@ final class AttachmentsStore: ObservableObject {
|
||||||
|
|
||||||
private let client: Client
|
private let client: Client
|
||||||
private let roster: Roster
|
private let roster: Roster
|
||||||
|
private var secured: Bool = false
|
||||||
|
|
||||||
private var messagesCancellable: AnyCancellable?
|
private var messagesCancellable: AnyCancellable?
|
||||||
|
private var chatCancellable: AnyCancellable?
|
||||||
private var processing: Set<String> = []
|
private var processing: Set<String> = []
|
||||||
|
|
||||||
init(roster: Roster, client: Client) {
|
init(roster: Roster, client: Client) {
|
||||||
|
@ -62,7 +64,7 @@ extension AttachmentsStore {
|
||||||
var message = Message.blank
|
var message = Message.blank
|
||||||
message.from = roster.bareJid
|
message.from = roster.bareJid
|
||||||
message.to = roster.contactBareJid
|
message.to = roster.contactBareJid
|
||||||
message.secure = true
|
message.secure = secured
|
||||||
|
|
||||||
switch item.type {
|
switch item.type {
|
||||||
case .photo:
|
case .photo:
|
||||||
|
@ -111,7 +113,7 @@ extension AttachmentsStore {
|
||||||
var message = Message.blank
|
var message = Message.blank
|
||||||
message.from = roster.bareJid
|
message.from = roster.bareJid
|
||||||
message.to = roster.contactBareJid
|
message.to = roster.contactBareJid
|
||||||
message.secure = true
|
message.secure = secured
|
||||||
|
|
||||||
let localName: String
|
let localName: String
|
||||||
let msgType: AttachmentType
|
let msgType: AttachmentType
|
||||||
|
@ -177,7 +179,7 @@ extension AttachmentsStore {
|
||||||
var message = Message.blank
|
var message = Message.blank
|
||||||
message.from = roster.bareJid
|
message.from = roster.bareJid
|
||||||
message.to = roster.contactBareJid
|
message.to = roster.contactBareJid
|
||||||
message.secure = true
|
message.secure = secured
|
||||||
message.contentType = .attachment(
|
message.contentType = .attachment(
|
||||||
Attachment(
|
Attachment(
|
||||||
type: localName.attachmentType,
|
type: localName.attachmentType,
|
||||||
|
@ -241,6 +243,18 @@ private extension AttachmentsStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chatCancellable = ValueObservation.tracking(Chat
|
||||||
|
.filter(Column("bareJid") == roster.bareJid && Column("contactBareJid") == roster.contactBareJid)
|
||||||
|
.fetchOne
|
||||||
|
)
|
||||||
|
.publisher(in: Database.shared.dbQueue, scheduling: .immediate)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { _ in
|
||||||
|
} receiveValue: { [weak self] chat in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.secured = chat?.encrypted ?? false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +270,7 @@ extension AttachmentsStore {
|
||||||
guard let localName = attachment.localPath else {
|
guard let localName = attachment.localPath else {
|
||||||
throw AppError.invalidLocalName
|
throw AppError.invalidLocalName
|
||||||
}
|
}
|
||||||
let remotePath = try await client.uploadFile(localName)
|
let remotePath = try await client.uploadFile(localName, needEncrypt: message.secure)
|
||||||
message.contentType = .attachment(
|
message.contentType = .attachment(
|
||||||
Attachment(
|
Attachment(
|
||||||
type: attachment.type,
|
type: attachment.type,
|
||||||
|
|
|
@ -10,8 +10,10 @@ final class MessagesStore: ObservableObject {
|
||||||
|
|
||||||
private(set) var roster: Roster
|
private(set) var roster: Roster
|
||||||
private let client: Client
|
private let client: Client
|
||||||
|
private var secured: Bool = false
|
||||||
|
|
||||||
private var messagesCancellable: AnyCancellable?
|
private var messagesCancellable: AnyCancellable?
|
||||||
|
private var chatCancellable: AnyCancellable?
|
||||||
private let archiver = ArchiveMessageFetcher()
|
private let archiver = ArchiveMessageFetcher()
|
||||||
|
|
||||||
init(roster: Roster, client: Client) {
|
init(roster: Roster, client: Client) {
|
||||||
|
@ -29,7 +31,7 @@ extension MessagesStore {
|
||||||
msg.from = roster.bareJid
|
msg.from = roster.bareJid
|
||||||
msg.to = roster.contactBareJid
|
msg.to = roster.contactBareJid
|
||||||
msg.body = message
|
msg.body = message
|
||||||
msg.secure = true
|
msg.secure = secured
|
||||||
|
|
||||||
// store as pending on db, and send
|
// store as pending on db, and send
|
||||||
do {
|
do {
|
||||||
|
@ -72,6 +74,18 @@ private extension MessagesStore {
|
||||||
await self.archiver.initialFetch(messages, self.roster, self.client)
|
await self.archiver.initialFetch(messages, self.roster, self.client)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
chatCancellable = ValueObservation.tracking(Chat
|
||||||
|
.filter(Column("bareJid") == roster.bareJid && Column("contactBareJid") == roster.contactBareJid)
|
||||||
|
.fetchOne
|
||||||
|
)
|
||||||
|
.publisher(in: Database.shared.dbQueue, scheduling: .immediate)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { _ in
|
||||||
|
} receiveValue: { [weak self] chat in
|
||||||
|
guard let self = self else { return }
|
||||||
|
self.secured = chat?.encrypted ?? false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,12 @@ struct Storage<T> {
|
||||||
|
|
||||||
// Storage
|
// Storage
|
||||||
private let kOmemoDeviceId = "conversations.classic.user.defaults.omemoDeviceId"
|
private let kOmemoDeviceId = "conversations.classic.user.defaults.omemoDeviceId"
|
||||||
|
private let kSecureChatsByDefault = "conversations.classic.user.defaults.secureChatsByDefault"
|
||||||
|
|
||||||
enum UserSettings {
|
enum UserSettings {
|
||||||
@Storage(key: kOmemoDeviceId, defaultValue: 0)
|
@Storage(key: kOmemoDeviceId, defaultValue: 0)
|
||||||
static var omemoDeviceId: UInt32
|
static var omemoDeviceId: UInt32
|
||||||
|
|
||||||
|
@Storage(key: kSecureChatsByDefault, defaultValue: false)
|
||||||
|
static var secureChatsByDefault: Bool
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue