// // TOTPKey.swift // OTPKit // // Created by Shadowfacts on 8/21/21. // import Foundation public struct TOTPKey: OTPKey, Equatable, Hashable, Codable { public let secret: Data public let period: Int public let digits: Int public let algorithm: OTPAlgorithm public let label: String? public let issuer: String // let image: Data? public init(secret: Data, period: Int, digits: Int, label: String?, issuer: String) { self.secret = secret self.period = period self.digits = digits self.algorithm = .SHA1 self.label = label self.issuer = issuer } } public extension TOTPKey { init?(urlComponents: URLComponents) { guard urlComponents.scheme == "otpauth", urlComponents.host == "totp", let queryItems = urlComponents.queryItems else { return nil } var label: String? var issuer: String var secret: Data? var period: Int = 30 var digits = 6 let path = urlComponents.path.drop(while: { $0 == "/" }) let components = path.components(separatedBy: ":") if components.count > 1 { issuer = components[0] label = components[1] } else { issuer = components[0] } for item in queryItems { guard let value = item.value else { continue } switch item.name.lowercased() { case "algorithm": if value.lowercased() != "sha1" { return nil } case "digits": if let newDigits = Int(value) { digits = newDigits } case "issuer": issuer = value case "period": if let newPeriod = Int(value) { period = newPeriod } case "secret": if let data = value.base32DecodedData { secret = data } default: continue } } guard let secret = secret else { return nil } self.issuer = issuer self.label = label self.secret = secret self.period = period self.digits = digits self.algorithm = .SHA1 } var url: URL { var components = URLComponents() components.scheme = "otpauth" components.host = "totp" if let label = label, !label.isEmpty { components.path = "/\(issuer):\(label)" } else { components.path = "/\(issuer)" } components.queryItems = [ URLQueryItem(name: "algorithm", value: "SHA1"), URLQueryItem(name: "digits", value: digits.description), URLQueryItem(name: "issuer", value: issuer), URLQueryItem(name: "period", value: period.description), URLQueryItem(name: "secret", value: secret.base32EncodedString), ] return components.url! } }