111 lines
3.1 KiB
Swift
111 lines
3.1 KiB
Swift
|
//
|
||
|
// 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!
|
||
|
}
|
||
|
|
||
|
}
|