Added the updated revoke & unrevoke endpoints

Added the updated revoke & unrevoke endpoints when removing and adding group members
Updated the group creation to upload the provided image and set it to the group correctly
pull/941/head
Morgan Pretty 2 years ago
parent 7e34c1609f
commit dffe321bc4

@ -332,7 +332,7 @@ final class NewClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegate
ModalActivityIndicatorViewController.present(fromViewController: navigationController!) { [weak self] _ in ModalActivityIndicatorViewController.present(fromViewController: navigationController!) { [weak self] _ in
MessageSender MessageSender
// .createLegacyClosedGroup(name: name, members: selectedProfiles.map { $0.0 }.asSet()) // .createLegacyClosedGroup(name: name, members: selectedProfiles.map { $0.0 }.asSet())
.createGroup(name: name, description: nil, displayPicture: nil, members: selectedProfiles) .createGroup(name: name, description: nil, displayPictureData: nil, members: selectedProfiles)
.subscribe(on: DispatchQueue.global(qos: .userInitiated)) .subscribe(on: DispatchQueue.global(qos: .userInitiated))
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sinkUntilComplete( .sinkUntilComplete(

@ -20,34 +20,42 @@ extension MessageSender {
public static func createGroup( public static func createGroup(
name: String, name: String,
description: String?, description: String?,
displayPicture: SignalAttachment?, displayPictureData: Data?,
members: [(String, Profile?)], members: [(String, Profile?)],
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<SessionThread, Error> { ) -> AnyPublisher<SessionThread, Error> {
typealias ImageUploadResponse = (downloadUrl: String, fileName: String, encryptionKey: Data)
return Just(()) return Just(())
.setFailureType(to: Error.self) .setFailureType(to: Error.self)
.flatMap { _ -> AnyPublisher<(url: String, filename: String, encryptionKey: Data)?, Error> in .flatMap { _ -> AnyPublisher<ImageUploadResponse?, Error> in
guard let displayPicture: SignalAttachment = displayPicture else { guard let displayPictureData: Data = displayPictureData else {
return Just(nil) return Just(nil)
.setFailureType(to: Error.self) .setFailureType(to: Error.self)
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
// TODO: Upload group image first
return Just(nil) return Deferred {
.setFailureType(to: Error.self) Future<ImageUploadResponse?, Error> { resolver in
.eraseToAnyPublisher() DisplayPictureManager.prepareAndUploadDisplayPicture(
queue: DispatchQueue.global(qos: .userInitiated),
imageData: displayPictureData,
success: { resolver(Result.success($0)) },
failure: { resolver(Result.failure($0)) },
using: dependencies
)
}
}.eraseToAnyPublisher()
} }
.flatMap { displayPictureInfo -> AnyPublisher<PreparedGroupData, Error> in .flatMap { displayPictureInfo -> AnyPublisher<PreparedGroupData, Error> in
dependencies[singleton: .storage].writePublisher(using: dependencies) { db -> PreparedGroupData in dependencies[singleton: .storage].writePublisher(using: dependencies) { db -> PreparedGroupData in
// Create and cache the libSession entries // Create and cache the libSession entries
let userSessionId: SessionId = getUserSessionId(db, using: dependencies)
let currentUserProfile: Profile = Profile.fetchOrCreateCurrentUser(db, using: dependencies)
let createdInfo: SessionUtil.CreatedGroupInfo = try SessionUtil.createGroup( let createdInfo: SessionUtil.CreatedGroupInfo = try SessionUtil.createGroup(
db, db,
name: name, name: name,
description: description, description: description,
displayPictureUrl: displayPictureInfo?.url, displayPictureUrl: displayPictureInfo?.downloadUrl,
displayPictureFilename: displayPictureInfo?.filename, displayPictureFilename: displayPictureInfo?.fileName,
displayPictureEncryptionKey: displayPictureInfo?.encryptionKey, displayPictureEncryptionKey: displayPictureInfo?.encryptionKey,
members: members, members: members,
using: dependencies using: dependencies
@ -402,9 +410,14 @@ extension MessageSender {
} }
/// Generate the data needed to send the new members invitations to the group /// Generate the data needed to send the new members invitations to the group
let memberJobData: [(id: String, profile: Profile?, jobDetails: GroupInviteMemberJob.Details)] = try members let memberJobData: [(id: String, profile: Profile?, jobDetails: GroupInviteMemberJob.Details, subaccountToken: [UInt8])] = try members
.map { id, profile in .map { id, profile in
// Generate authData for the newly added member // Generate authData for the newly added member
let subaccountToken: [UInt8] = try SessionUtil.generateSubaccountToken(
groupSessionId: sessionId,
memberId: id,
using: dependencies
)
let memberAuthInfo: Authentication.Info = try SessionUtil.generateAuthData( let memberAuthInfo: Authentication.Info = try SessionUtil.generateAuthData(
groupSessionId: sessionId, groupSessionId: sessionId,
memberId: id, memberId: id,
@ -415,7 +428,7 @@ extension MessageSender {
authInfo: memberAuthInfo authInfo: memberAuthInfo
) )
return (id, profile, inviteDetails) return (id, profile, inviteDetails, subaccountToken)
} }
/// Unrevoke the newly added members just in case they had previously gotten their access to the group /// Unrevoke the newly added members just in case they had previously gotten their access to the group
@ -427,9 +440,9 @@ extension MessageSender {
try? SnodeAPI try? SnodeAPI
.preparedBatch( .preparedBatch(
db, db,
requests: try memberJobDataChunk.map { id, _, jobDetails in requests: try memberJobDataChunk.map { id, _, _, subaccountToken in
try SnodeAPI.preparedUnrevokeSubaccount( try SnodeAPI.preparedUnrevokeSubaccount(
subaccountToUnrevoke: jobDetails.memberAuthData.toHexString(), subaccountToUnrevoke: subaccountToken,
authMethod: Authentication.groupAdmin( authMethod: Authentication.groupAdmin(
groupSessionId: sessionId, groupSessionId: sessionId,
ed25519SecretKey: Array(groupIdentityPrivateKey) ed25519SecretKey: Array(groupIdentityPrivateKey)
@ -447,7 +460,7 @@ extension MessageSender {
} }
/// Make the required changes for each added member /// Make the required changes for each added member
try memberJobData.forEach { id, profile, inviteJobDetails in try memberJobData.forEach { id, profile, inviteJobDetails, _ in
/// Add the member to the database /// Add the member to the database
try GroupMember( try GroupMember(
groupId: sessionId.hexString, groupId: sessionId.hexString,
@ -518,6 +531,11 @@ extension MessageSender {
.fetchOne(db) .fetchOne(db)
else { throw MessageSenderError.invalidClosedGroupUpdate } else { throw MessageSenderError.invalidClosedGroupUpdate }
let subaccountToken: [UInt8] = try SessionUtil.generateSubaccountToken(
groupSessionId: sessionId,
memberId: memberId,
using: dependencies
)
let inviteDetails: GroupInviteMemberJob.Details = try GroupInviteMemberJob.Details( let inviteDetails: GroupInviteMemberJob.Details = try GroupInviteMemberJob.Details(
memberSessionIdHexString: memberId, memberSessionIdHexString: memberId,
authInfo: try SessionUtil.generateAuthData( authInfo: try SessionUtil.generateAuthData(
@ -531,7 +549,7 @@ extension MessageSender {
/// unrevoke request when initially added them failed (fire-and-forget this request, we don't want it to be blocking) /// unrevoke request when initially added them failed (fire-and-forget this request, we don't want it to be blocking)
try SnodeAPI try SnodeAPI
.preparedUnrevokeSubaccount( .preparedUnrevokeSubaccount(
subaccountToUnrevoke: inviteDetails.memberAuthData.toHexString(), subaccountToUnrevoke: subaccountToken,
authMethod: Authentication.groupAdmin( authMethod: Authentication.groupAdmin(
groupSessionId: sessionId, groupSessionId: sessionId,
ed25519SecretKey: Array(groupIdentityPrivateKey) ed25519SecretKey: Array(groupIdentityPrivateKey)
@ -658,16 +676,15 @@ extension MessageSender {
requests: memberIdsChunk.compactMap { id -> HTTP.PreparedRequest<Void>? in requests: memberIdsChunk.compactMap { id -> HTTP.PreparedRequest<Void>? in
// Generate authData for the removed member // Generate authData for the removed member
guard guard
let memberAuthInfo: Authentication.Info = try? SessionUtil.generateAuthData( let subaccountToken: [UInt8] = try? SessionUtil.generateSubaccountToken(
groupSessionId: groupSessionId, groupSessionId: groupSessionId,
memberId: id, memberId: id,
using: dependencies using: dependencies
), )
case .groupMember(_, let memberAuthData) = memberAuthInfo
else { return nil } else { return nil }
return try? SnodeAPI.preparedRevokeSubaccount( return try? SnodeAPI.preparedRevokeSubaccount(
subaccountToRevoke: memberAuthData.toHexString(), subaccountToRevoke: subaccountToken,
authMethod: Authentication.groupAdmin( authMethod: Authentication.groupAdmin(
groupSessionId: groupSessionId, groupSessionId: groupSessionId,
ed25519SecretKey: Array(groupIdentityPrivateKey) ed25519SecretKey: Array(groupIdentityPrivateKey)

@ -91,6 +91,22 @@ internal extension SessionUtil {
} ?? { throw SessionUtilError.invalidConfigObject }() } ?? { throw SessionUtilError.invalidConfigObject }()
} }
static func generateSubaccountToken(
groupSessionId: SessionId,
memberId: String,
using dependencies: Dependencies
) throws -> [UInt8] {
try dependencies[singleton: .crypto].perform(
.subaccountToken(
config: dependencies[cache: .sessionUtil]
.config(for: .groupKeys, sessionId: groupSessionId)
.wrappedValue,
groupSessionId: groupSessionId,
memberId: memberId
)
)
}
static func generateAuthData( static func generateAuthData(
groupSessionId: SessionId, groupSessionId: SessionId,
memberId: String, memberId: String,

@ -1238,7 +1238,7 @@ extension SessionUtil {
) \(lastKeyPair, asSubquery: true) ON \(lastKeyPair[.threadId]) = \(closedGroup[.threadId]) ) \(lastKeyPair, asSubquery: true) ON \(lastKeyPair[.threadId]) = \(closedGroup[.threadId])
LEFT JOIN \(disappearingConfig) ON \(disappearingConfig[.threadId]) = \(closedGroup[.threadId]) LEFT JOIN \(disappearingConfig) ON \(disappearingConfig[.threadId]) = \(closedGroup[.threadId])
WHERE \(SQL("\(closedGroup[.threadId]) LIKE '\(SessionId.Prefix.standard)%'")) WHERE \(closedGroup[.threadId]) LIKE '\(SessionId.Prefix.standard)%'
""" """
let legacyGroupInfoNoMembers: [LegacyGroupInfo] = try request let legacyGroupInfoNoMembers: [LegacyGroupInfo] = try request

@ -4,6 +4,32 @@ import Foundation
import SessionUtil import SessionUtil
import SessionUtilitiesKit import SessionUtilitiesKit
public extension Crypto.Action {
static func subaccountToken(
config: SessionUtil.Config?,
groupSessionId: SessionId,
memberId: String
) -> Crypto.Action {
return Crypto.Action(
id: "subaccountToken",
args: [config, groupSessionId, memberId]
) {
guard case .groupKeys(let conf, _, _) = config else { throw SessionUtilError.invalidConfigObject }
var cMemberId: [CChar] = memberId.cArray
var tokenData: [UInt8] = [UInt8](repeating: 0, count: SessionUtil.sizeSubaccountBytes)
guard groups_keys_swarm_subaccount_token(
conf,
&cMemberId,
&tokenData
) else { throw SessionUtilError.failedToMakeSubAccountInGroup }
return tokenData
}
}
}
public extension Crypto.AuthenticationInfo { public extension Crypto.AuthenticationInfo {
static func memberAuthData( static func memberAuthData(
config: SessionUtil.Config?, config: SessionUtil.Config?,

@ -244,14 +244,14 @@ public struct DisplayPictureManager {
public static func prepareAndUploadDisplayPicture( public static func prepareAndUploadDisplayPicture(
queue: DispatchQueue, queue: DispatchQueue,
imageData: Data, imageData: Data,
success: @escaping ((downloadUrl: String, fileName: String, profileKey: Data)) -> (), success: @escaping ((downloadUrl: String, fileName: String, encryptionKey: Data)) -> (),
failure: ((DisplayPictureError) -> ())? = nil, failure: ((DisplayPictureError) -> ())? = nil,
using dependencies: Dependencies using dependencies: Dependencies
) { ) {
queue.async { queue.async {
// If the profile avatar was updated or removed then encrypt with a new profile key // If the profile avatar was updated or removed then encrypt with a new profile key
// to ensure that other users know that our profile picture was updated // to ensure that other users know that our profile picture was updated
let newProfileKey: Data let newEncryptionKey: Data
let finalImageData: Data let finalImageData: Data
let fileExtension: String let fileExtension: String
@ -306,7 +306,9 @@ public struct DisplayPictureManager {
return data return data
}() }()
newProfileKey = try Randomness.generateRandomBytes(numberBytes: DisplayPictureManager.aes256KeyByteLength) newEncryptionKey = try Randomness.generateRandomBytes(
numberBytes: DisplayPictureManager.aes256KeyByteLength
)
fileExtension = { fileExtension = {
switch guessedFormat { switch guessedFormat {
case .gif: return "gif" // stringlint:disable case .gif: return "gif" // stringlint:disable
@ -338,7 +340,7 @@ public struct DisplayPictureManager {
} }
// Encrypt the avatar for upload // Encrypt the avatar for upload
guard let encryptedData: Data = DisplayPictureManager.encryptData(data: finalImageData, key: newProfileKey) else { guard let encryptedData: Data = DisplayPictureManager.encryptData(data: finalImageData, key: newEncryptionKey) else {
SNLog("Updating service with profile failed.") SNLog("Updating service with profile failed.")
failure?(.encryptionFailed) failure?(.encryptionFailed)
return return
@ -373,7 +375,7 @@ public struct DisplayPictureManager {
dependencies.mutate(cache: .displayPicture) { $0.imageData[fileName] = finalImageData } dependencies.mutate(cache: .displayPicture) { $0.imageData[fileName] = finalImageData }
SNLog("Successfully uploaded avatar image.") SNLog("Successfully uploaded avatar image.")
success((downloadUrl, fileName, newProfileKey)) success((downloadUrl, fileName, newEncryptionKey))
} }
) )
} }

@ -172,7 +172,7 @@ class MessageSenderGroupsSpec: QuickSpec {
.createGroup( .createGroup(
name: "TestGroupName", name: "TestGroupName",
description: nil, description: nil,
displayPicture: nil, displayPictureData: nil,
members: [ members: [
("051111111111111111111111111111111111111111111111111111111111111111", nil) ("051111111111111111111111111111111111111111111111111111111111111111", nil)
], ],
@ -199,7 +199,7 @@ class MessageSenderGroupsSpec: QuickSpec {
.createGroup( .createGroup(
name: "Test", name: "Test",
description: nil, description: nil,
displayPicture: nil, displayPictureData: nil,
members: [ members: [
("051111111111111111111111111111111111111111111111111111111111111111", nil) ("051111111111111111111111111111111111111111111111111111111111111111", nil)
], ],
@ -226,7 +226,7 @@ class MessageSenderGroupsSpec: QuickSpec {
.createGroup( .createGroup(
name: "TestGroupName", name: "TestGroupName",
description: nil, description: nil,
displayPicture: nil, displayPictureData: nil,
members: [ members: [
("051111111111111111111111111111111111111111111111111111111111111111", nil) ("051111111111111111111111111111111111111111111111111111111111111111", nil)
], ],
@ -253,7 +253,7 @@ class MessageSenderGroupsSpec: QuickSpec {
.createGroup( .createGroup(
name: "TestGroupName", name: "TestGroupName",
description: nil, description: nil,
displayPicture: nil, displayPictureData: nil,
members: [ members: [
("051111111111111111111111111111111111111111111111111111111111111111", nil) ("051111111111111111111111111111111111111111111111111111111111111111", nil)
], ],
@ -286,7 +286,7 @@ class MessageSenderGroupsSpec: QuickSpec {
.createGroup( .createGroup(
name: "TestGroupName", name: "TestGroupName",
description: nil, description: nil,
displayPicture: nil, displayPictureData: nil,
members: [ members: [
("051111111111111111111111111111111111111111111111111111111111111111", nil) ("051111111111111111111111111111111111111111111111111111111111111111", nil)
], ],
@ -361,7 +361,7 @@ class MessageSenderGroupsSpec: QuickSpec {
.createGroup( .createGroup(
name: "TestGroupName", name: "TestGroupName",
description: nil, description: nil,
displayPicture: nil, displayPictureData: nil,
members: [ members: [
("051111111111111111111111111111111111111111111111111111111111111111", nil) ("051111111111111111111111111111111111111111111111111111111111111111", nil)
], ],
@ -395,7 +395,7 @@ class MessageSenderGroupsSpec: QuickSpec {
.createGroup( .createGroup(
name: "TestGroupName", name: "TestGroupName",
description: nil, description: nil,
displayPicture: nil, displayPictureData: nil,
members: [ members: [
("051111111111111111111111111111111111111111111111111111111111111111", nil) ("051111111111111111111111111111111111111111111111111111111111111111", nil)
], ],
@ -413,7 +413,7 @@ class MessageSenderGroupsSpec: QuickSpec {
.createGroup( .createGroup(
name: "TestGroupName", name: "TestGroupName",
description: nil, description: nil,
displayPicture: nil, displayPictureData: nil,
members: [ members: [
("051111111111111111111111111111111111111111111111111111111111111111", nil) ("051111111111111111111111111111111111111111111111111111111111111111", nil)
], ],
@ -442,7 +442,7 @@ class MessageSenderGroupsSpec: QuickSpec {
.createGroup( .createGroup(
name: "TestGroupName", name: "TestGroupName",
description: nil, description: nil,
displayPicture: nil, displayPictureData: nil,
members: [ members: [
("051111111111111111111111111111111111111111111111111111111111111111", nil) ("051111111111111111111111111111111111111111111111111111111111111111", nil)
], ],

@ -9,24 +9,29 @@ extension SnodeAPI {
case subaccountToRevoke = "revoke" case subaccountToRevoke = "revoke"
} }
let subaccountToRevoke: String let subaccountToRevoke: [UInt8]
override var verificationBytes: [UInt8] { override var verificationBytes: [UInt8] {
/// Ed25519 signature of `("revoke_subaccount" || subaccount)`; this signs the subkey tag, /// Ed25519 signature of `("revoke_subaccount" || timestamp || SUBACCOUNT_TAG_BYTES)`; this signs the subkey tag,
/// using `pubkey` to sign. Must be base64 encoded for json requests; binary for OMQ requests. /// using `pubkey` to sign. Must be base64 encoded for json requests; binary for OMQ requests.
SnodeAPI.Endpoint.revokeSubaccount.path.bytes SnodeAPI.Endpoint.revokeSubaccount.path.bytes
.appending(contentsOf: subaccountToRevoke.bytes) .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes)
.appending(contentsOf: subaccountToRevoke)
} }
// MARK: - Init // MARK: - Init
public init( public init(
subaccountToRevoke: String, subaccountToRevoke: [UInt8],
authMethod: AuthenticationMethod authMethod: AuthenticationMethod,
timestampMs: UInt64
) { ) {
self.subaccountToRevoke = subaccountToRevoke self.subaccountToRevoke = subaccountToRevoke
super.init(authMethod: authMethod) super.init(
authMethod: authMethod,
timestampMs: timestampMs
)
} }
// MARK: - Coding // MARK: - Coding
@ -34,7 +39,8 @@ extension SnodeAPI {
override public func encode(to encoder: Encoder) throws { override public func encode(to encoder: Encoder) throws {
var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self) var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
try container.encode(subaccountToRevoke, forKey: .subaccountToRevoke) /// The `subaccountToRevoke` should be sent as a hex string
try container.encode(subaccountToRevoke.toHexString(), forKey: .subaccountToRevoke)
try super.encode(to: encoder) try super.encode(to: encoder)
} }

@ -9,7 +9,7 @@ public class RevokeSubaccountResponse: SnodeRecursiveResponse<SnodeSwarmItem> {}
// MARK: - ValidatableResponse // MARK: - ValidatableResponse
extension RevokeSubaccountResponse: ValidatableResponse { extension RevokeSubaccountResponse: ValidatableResponse {
typealias ValidationData = String typealias ValidationData = (subaccountToRevoke: [UInt8], timestampMs: UInt64)
typealias ValidationResponse = Bool typealias ValidationResponse = Bool
/// All responses in the swarm must be valid /// All responses in the swarm must be valid
@ -17,7 +17,7 @@ extension RevokeSubaccountResponse: ValidatableResponse {
internal func validResultMap( internal func validResultMap(
publicKey: String, publicKey: String,
validationData: String, validationData: (subaccountToRevoke: [UInt8], timestampMs: UInt64),
using dependencies: Dependencies using dependencies: Dependencies
) throws -> [String: Bool] { ) throws -> [String: Bool] {
let validationMap: [String: Bool] = try swarm.reduce(into: [:]) { result, next in let validationMap: [String: Bool] = try swarm.reduce(into: [:]) { result, next in
@ -35,10 +35,11 @@ extension RevokeSubaccountResponse: ValidatableResponse {
return return
} }
/// Signature of `( PUBKEY_HEX || SUBACCOUNT_TAG_BYTES )` where `SUBACCOUNT_TAG_BYTES` is the /// Signature of `( PUBKEY_HEX || timestamp || SUBACCOUNT_TAG_BYTES )` where `SUBACCOUNT_TAG_BYTES` is the
/// requested subkey tag for revocation /// requested subkey tag for revocation
let verificationBytes: [UInt8] = publicKey.bytes let verificationBytes: [UInt8] = publicKey.bytes
.appending(contentsOf: validationData.bytes) .appending(contentsOf: "\(validationData.timestampMs)".data(using: .ascii)?.bytes)
.appending(contentsOf: validationData.subaccountToRevoke)
let isValid: Bool = dependencies[singleton: .crypto].verify( let isValid: Bool = dependencies[singleton: .crypto].verify(
.signature( .signature(

@ -102,7 +102,6 @@ extension SendMessagesResponse: ValidatableResponse {
/// Signature of `hash` signed by the node's ed25519 pubkey /// Signature of `hash` signed by the node's ed25519 pubkey
let verificationBytes: [UInt8] = hash.bytes let verificationBytes: [UInt8] = hash.bytes
result[next.key] = dependencies[singleton: .crypto].verify( result[next.key] = dependencies[singleton: .crypto].verify(
.signature( .signature(
message: verificationBytes, message: verificationBytes,

@ -9,24 +9,29 @@ extension SnodeAPI {
case subaccountToUnrevoke = "unrevoke" case subaccountToUnrevoke = "unrevoke"
} }
let subaccountToUnrevoke: String let subaccountToUnrevoke: [UInt8]
override var verificationBytes: [UInt8] { override var verificationBytes: [UInt8] {
/// Ed25519 signature of `("unrevoke_subaccount" || subaccount)`; this signs the subkey tag, /// Ed25519 signature of `("unrevoke_subaccount" || timestamp || subaccount)`; this signs the subkey tag,
/// using `pubkey` to sign. Must be base64 encoded for json requests; binary for OMQ requests. /// using `pubkey` to sign. Must be base64 encoded for json requests; binary for OMQ requests.
SnodeAPI.Endpoint.unrevokeSubaccount.path.bytes SnodeAPI.Endpoint.unrevokeSubaccount.path.bytes
.appending(contentsOf: subaccountToUnrevoke.bytes) .appending(contentsOf: timestampMs.map { "\($0)" }?.data(using: .ascii)?.bytes)
.appending(contentsOf: subaccountToUnrevoke)
} }
// MARK: - Init // MARK: - Init
public init( public init(
subaccountToUnrevoke: String, subaccountToUnrevoke: [UInt8],
authMethod: AuthenticationMethod authMethod: AuthenticationMethod,
timestampMs: UInt64
) { ) {
self.subaccountToUnrevoke = subaccountToUnrevoke self.subaccountToUnrevoke = subaccountToUnrevoke
super.init(authMethod: authMethod) super.init(
authMethod: authMethod,
timestampMs: timestampMs
)
} }
// MARK: - Coding // MARK: - Coding
@ -34,7 +39,8 @@ extension SnodeAPI {
override public func encode(to encoder: Encoder) throws { override public func encode(to encoder: Encoder) throws {
var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self) var container: KeyedEncodingContainer<CodingKeys> = encoder.container(keyedBy: CodingKeys.self)
try container.encode(subaccountToUnrevoke, forKey: .subaccountToUnrevoke) /// The `subaccountToRevoke` should be sent as a hex string
try container.encode(subaccountToUnrevoke.toHexString(), forKey: .subaccountToUnrevoke)
try super.encode(to: encoder) try super.encode(to: encoder)
} }

@ -9,7 +9,7 @@ public class UnrevokeSubaccountResponse: SnodeRecursiveResponse<SnodeSwarmItem>
// MARK: - ValidatableResponse // MARK: - ValidatableResponse
extension UnrevokeSubaccountResponse: ValidatableResponse { extension UnrevokeSubaccountResponse: ValidatableResponse {
typealias ValidationData = String typealias ValidationData = (subaccountToUnrevoke: [UInt8], timestampMs: UInt64)
typealias ValidationResponse = Bool typealias ValidationResponse = Bool
/// All responses in the swarm must be valid /// All responses in the swarm must be valid
@ -17,7 +17,7 @@ extension UnrevokeSubaccountResponse: ValidatableResponse {
internal func validResultMap( internal func validResultMap(
publicKey: String, publicKey: String,
validationData: String, validationData: (subaccountToUnrevoke: [UInt8], timestampMs: UInt64),
using dependencies: Dependencies using dependencies: Dependencies
) throws -> [String: Bool] { ) throws -> [String: Bool] {
let validationMap: [String: Bool] = try swarm.reduce(into: [:]) { result, next in let validationMap: [String: Bool] = try swarm.reduce(into: [:]) { result, next in
@ -35,10 +35,11 @@ extension UnrevokeSubaccountResponse: ValidatableResponse {
return return
} }
/// Signature of `( PUBKEY_HEX || SUBKEY_TAG_BYTES )` where `SUBKEY_TAG_BYTES` is the /// Signature of `( PUBKEY_HEX || timestamp || SUBKEY_TAG_BYTES )` where `SUBKEY_TAG_BYTES` is the
/// requested subkey tag for revocation /// requested subkey tag for revocation
let verificationBytes: [UInt8] = publicKey.bytes let verificationBytes: [UInt8] = publicKey.bytes
.appending(contentsOf: validationData.bytes) .appending(contentsOf: "\(validationData.timestampMs)".data(using: .ascii)?.bytes)
.appending(contentsOf: validationData.subaccountToUnrevoke)
let isValid: Bool = dependencies[singleton: .crypto].verify( let isValid: Bool = dependencies[singleton: .crypto].verify(
.signature( .signature(

@ -658,10 +658,12 @@ public final class SnodeAPI {
} }
public static func preparedRevokeSubaccount( public static func preparedRevokeSubaccount(
subaccountToRevoke: String, subaccountToRevoke: [UInt8],
authMethod: AuthenticationMethod, authMethod: AuthenticationMethod,
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies = Dependencies()
) throws -> HTTP.PreparedRequest<Void> { ) throws -> HTTP.PreparedRequest<Void> {
let timestampMs: UInt64 = UInt64(SnodeAPI.currentOffsetTimestampMs(using: dependencies))
return try SnodeAPI return try SnodeAPI
.prepareRequest( .prepareRequest(
request: Request( request: Request(
@ -669,7 +671,8 @@ public final class SnodeAPI {
publicKey: authMethod.sessionId.hexString, publicKey: authMethod.sessionId.hexString,
body: RevokeSubaccountRequest( body: RevokeSubaccountRequest(
subaccountToRevoke: subaccountToRevoke, subaccountToRevoke: subaccountToRevoke,
authMethod: authMethod authMethod: authMethod,
timestampMs: timestampMs
) )
), ),
responseType: RevokeSubaccountResponse.self responseType: RevokeSubaccountResponse.self
@ -677,7 +680,7 @@ public final class SnodeAPI {
.tryMap { _, response -> Void in .tryMap { _, response -> Void in
try response.validateResultMap( try response.validateResultMap(
publicKey: authMethod.sessionId.hexString, publicKey: authMethod.sessionId.hexString,
validationData: subaccountToRevoke, validationData: (subaccountToRevoke, timestampMs),
using: dependencies using: dependencies
) )
@ -686,10 +689,12 @@ public final class SnodeAPI {
} }
public static func preparedUnrevokeSubaccount( public static func preparedUnrevokeSubaccount(
subaccountToUnrevoke: String, subaccountToUnrevoke: [UInt8],
authMethod: AuthenticationMethod, authMethod: AuthenticationMethod,
using dependencies: Dependencies = Dependencies() using dependencies: Dependencies = Dependencies()
) throws -> HTTP.PreparedRequest<Void> { ) throws -> HTTP.PreparedRequest<Void> {
let timestampMs: UInt64 = UInt64(SnodeAPI.currentOffsetTimestampMs(using: dependencies))
return try SnodeAPI return try SnodeAPI
.prepareRequest( .prepareRequest(
request: Request( request: Request(
@ -697,7 +702,8 @@ public final class SnodeAPI {
publicKey: authMethod.sessionId.hexString, publicKey: authMethod.sessionId.hexString,
body: UnrevokeSubaccountRequest( body: UnrevokeSubaccountRequest(
subaccountToUnrevoke: subaccountToUnrevoke, subaccountToUnrevoke: subaccountToUnrevoke,
authMethod: authMethod authMethod: authMethod,
timestampMs: timestampMs
) )
), ),
responseType: UnrevokeSubaccountResponse.self responseType: UnrevokeSubaccountResponse.self
@ -705,7 +711,7 @@ public final class SnodeAPI {
.tryMap { _, response -> Void in .tryMap { _, response -> Void in
try response.validateResultMap( try response.validateResultMap(
publicKey: authMethod.sessionId.hexString, publicKey: authMethod.sessionId.hexString,
validationData: subaccountToUnrevoke, validationData: (subaccountToUnrevoke, timestampMs),
using: dependencies using: dependencies
) )

@ -77,6 +77,6 @@ public extension SQLInterpolation {
/// let request: SQLRequest<User> = "SELECT * FROM \(user) WHERE \(user[.id]) LIKE '\(SessionId.Prefix.standard)%'" /// let request: SQLRequest<User> = "SELECT * FROM \(user) WHERE \(user[.id]) LIKE '\(SessionId.Prefix.standard)%'"
@_disfavoredOverload @_disfavoredOverload
mutating func appendInterpolation(_ idPrefix: SessionId.Prefix) { mutating func appendInterpolation(_ idPrefix: SessionId.Prefix) {
appendLiteral("\(SQL(stringLiteral: "\(idPrefix.rawValue)"))") appendLiteral(idPrefix.rawValue)
} }
} }

@ -56,6 +56,7 @@ public class Dependencies {
/// in `Dependencies.cacheInstances` so that we can be reliably certail we aren't accessing some /// in `Dependencies.cacheInstances` so that we can be reliably certail we aren't accessing some
/// random instance that will go out of memory as soon as the mutation is completed /// random instance that will go out of memory as soon as the mutation is completed
getValueSettingIfNull(cache: cache, &Dependencies.cacheInstances) getValueSettingIfNull(cache: cache, &Dependencies.cacheInstances)
let cacheWrapper: Atomic<MutableCacheType> = ( let cacheWrapper: Atomic<MutableCacheType> = (
Dependencies.cacheInstances.wrappedValue[cache.identifier] ?? Dependencies.cacheInstances.wrappedValue[cache.identifier] ??
Atomic(cache.mutableInstance(cache.createInstance(self))) // Should never be called Atomic(cache.mutableInstance(cache.createInstance(self))) // Should never be called

Loading…
Cancel
Save