feat: add core KDBX data models
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Attachment: Identifiable, Equatable {
|
||||||
|
public var id: UUID
|
||||||
|
public var name: String
|
||||||
|
public var data: Data
|
||||||
|
|
||||||
|
public init(id: UUID = UUID(), name: String, data: Data) {
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.data = data
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct CustomField: Identifiable, Equatable {
|
||||||
|
public var id: UUID
|
||||||
|
public var key: String
|
||||||
|
public var value: ProtectedString
|
||||||
|
|
||||||
|
public init(id: UUID = UUID(), key: String, value: ProtectedString) {
|
||||||
|
self.id = id
|
||||||
|
self.key = key
|
||||||
|
self.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct DatabaseMetadata: Equatable {
|
||||||
|
public var name: String
|
||||||
|
public var description: String
|
||||||
|
public init(name: String, description: String = "") {
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Entry: Identifiable, Equatable, Hashable {
|
||||||
|
public var id: UUID
|
||||||
|
public var title: String
|
||||||
|
public var username: String
|
||||||
|
public var password: ProtectedString
|
||||||
|
public var url: String
|
||||||
|
public var notes: String
|
||||||
|
public var customFields: [CustomField]
|
||||||
|
public var attachments: [Attachment]
|
||||||
|
public var totp: TOTPConfig?
|
||||||
|
public var tags: [String]
|
||||||
|
public var iconIndex: Int
|
||||||
|
public var expiryDate: Date?
|
||||||
|
public var creationDate: Date
|
||||||
|
public var modificationDate: Date
|
||||||
|
public var history: [Entry]
|
||||||
|
|
||||||
|
public init(
|
||||||
|
id: UUID = UUID(),
|
||||||
|
title: String = "",
|
||||||
|
username: String = "",
|
||||||
|
password: ProtectedString = ProtectedString("", isProtected: true),
|
||||||
|
url: String = "",
|
||||||
|
notes: String = "",
|
||||||
|
customFields: [CustomField] = [],
|
||||||
|
attachments: [Attachment] = [],
|
||||||
|
totp: TOTPConfig? = nil,
|
||||||
|
tags: [String] = [],
|
||||||
|
iconIndex: Int = 0,
|
||||||
|
expiryDate: Date? = nil,
|
||||||
|
creationDate: Date = Date(),
|
||||||
|
modificationDate: Date = Date(),
|
||||||
|
history: [Entry] = []
|
||||||
|
) {
|
||||||
|
self.id = id
|
||||||
|
self.title = title
|
||||||
|
self.username = username
|
||||||
|
self.password = password
|
||||||
|
self.url = url
|
||||||
|
self.notes = notes
|
||||||
|
self.customFields = customFields
|
||||||
|
self.attachments = attachments
|
||||||
|
self.totp = totp
|
||||||
|
self.tags = tags
|
||||||
|
self.iconIndex = iconIndex
|
||||||
|
self.expiryDate = expiryDate
|
||||||
|
self.creationDate = creationDate
|
||||||
|
self.modificationDate = modificationDate
|
||||||
|
self.history = history
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hashable by ID so SwiftUI List(selection:) works without requiring all fields to be Hashable.
|
||||||
|
public func hash(into hasher: inout Hasher) { hasher.combine(id) }
|
||||||
|
public static func == (lhs: Entry, rhs: Entry) -> Bool { lhs.id == rhs.id }
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct Group: Identifiable, Equatable, Hashable {
|
||||||
|
public var id: UUID
|
||||||
|
public var name: String
|
||||||
|
public var iconIndex: Int
|
||||||
|
public var subgroups: [Group]
|
||||||
|
public var entries: [Entry]
|
||||||
|
|
||||||
|
public init(
|
||||||
|
id: UUID = UUID(),
|
||||||
|
name: String,
|
||||||
|
iconIndex: Int = 0,
|
||||||
|
subgroups: [Group] = [],
|
||||||
|
entries: [Entry] = []
|
||||||
|
) {
|
||||||
|
self.id = id
|
||||||
|
self.name = name
|
||||||
|
self.iconIndex = iconIndex
|
||||||
|
self.subgroups = subgroups
|
||||||
|
self.entries = entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hashable by ID so SwiftUI List(selection:) works without hashing the full tree.
|
||||||
|
public func hash(into hasher: inout Hasher) { hasher.combine(id) }
|
||||||
|
public static func == (lhs: Group, rhs: Group) -> Bool { lhs.id == rhs.id }
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public struct KDBXDatabase: Equatable {
|
||||||
|
public var metadata: DatabaseMetadata
|
||||||
|
public var root: Group
|
||||||
|
|
||||||
|
public init(metadata: DatabaseMetadata, root: Group) {
|
||||||
|
self.metadata = metadata
|
||||||
|
self.root = root
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public enum TOTPAlgorithm: String, Equatable, CaseIterable {
|
||||||
|
case sha1 = "SHA1"
|
||||||
|
case sha256 = "SHA256"
|
||||||
|
case sha512 = "SHA512"
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct TOTPConfig: Equatable {
|
||||||
|
public var secret: String
|
||||||
|
public var period: Int
|
||||||
|
public var digits: Int
|
||||||
|
public var algorithm: TOTPAlgorithm
|
||||||
|
|
||||||
|
public init(secret: String, period: Int = 30, digits: Int = 6, algorithm: TOTPAlgorithm = .sha1) {
|
||||||
|
self.secret = secret
|
||||||
|
self.period = period
|
||||||
|
self.digits = digits
|
||||||
|
self.algorithm = algorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses an otpauth://totp/ URI (standard KeePass TOTP storage format).
|
||||||
|
public static func parse(from uri: String) -> TOTPConfig? {
|
||||||
|
guard let url = URL(string: uri),
|
||||||
|
url.scheme == "otpauth",
|
||||||
|
url.host == "totp",
|
||||||
|
let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
|
||||||
|
else { return nil }
|
||||||
|
let params = Dictionary(
|
||||||
|
uniqueKeysWithValues: (components.queryItems ?? []).compactMap { item in
|
||||||
|
item.value.map { (item.name, $0) }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
guard let secret = params["secret"] else { return nil }
|
||||||
|
let period = Int(params["period"] ?? "30") ?? 30
|
||||||
|
let digits = Int(params["digits"] ?? "6") ?? 6
|
||||||
|
let algo: TOTPAlgorithm
|
||||||
|
switch params["algorithm"]?.uppercased() {
|
||||||
|
case "SHA256": algo = .sha256
|
||||||
|
case "SHA512": algo = .sha512
|
||||||
|
default: algo = .sha1
|
||||||
|
}
|
||||||
|
return TOTPConfig(secret: secret, period: period, digits: digits, algorithm: algo)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user