Skip to content

Instantly share code, notes, and snippets.

@khanlou
Created August 12, 2017 19:57
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save khanlou/641e1549fe0ef20a0c063ac9f3aa6b9a to your computer and use it in GitHub Desktop.
Save khanlou/641e1549fe0ef20a0c063ac9f3aa6b9a to your computer and use it in GitHub Desktop.
public struct Credentials {
public init(key: String, secret: String) {
self.key = key
self.secret = secret
}
public let key: String
public let secret: String
}
struct OAuthSignatureGenerator {
let consumerCredentials: Credentials
let credentials: Credentials
let request: URLRequest
let timestamp: Date
let nonce: String
let dataEncoding: String.Encoding = .utf8
public enum OAuth {
static let version = "1.0"
static let signatureMethod = "HMAC-SHA1"
}
public init(consumerCredentials: Credentials, credentials: Credentials, request: URLRequest, timestamp: Date = Date(), nonce: String = UUID().uuidString) {
self.consumerCredentials = consumerCredentials
self.credentials = credentials
self.request = request
self.timestamp = timestamp
self.nonce = nonce
}
var method: String {
return request.httpMethod ?? ""
}
var url: URL {
guard let url = request.url else { fatalError("URL unwrapping in OAuthBehavior failed.")}
return url
}
var urlWithNoQueryParams: URL {
var urlComponents = URLComponents(string: url.absoluteString)
urlComponents?.queryItems = nil
guard let urlWithNoQueryParams = urlComponents?.url else { fatalError("URL unwrapping in OAuthBehavior failed.") }
return urlWithNoQueryParams
}
var bodyParameters: [String: Any]? {
do {
return try JSONSerialization.jsonObject(with: request.httpBody.unwrap()) as? [String: Any]
} catch {
return nil
}
}
var queryParameters: [String: Any]? {
return URLComponents(string: url.absoluteString)?
.queryItems?
.reduce([String: Any](), { dict, queryItem in
dict.dictionaryByAdding(queryItem.name, value: queryItem.value ?? "")
})
}
var parameters: [String: Any] {
return bodyParameters ?? queryParameters ?? [:]
}
var authorizationParameters: [String: Any] {
return [
"oauth_version": OAuth.version,
"oauth_signature_method": OAuth.signatureMethod,
"oauth_consumer_key": consumerCredentials.key,
"oauth_timestamp": String(Int(timestamp.timeIntervalSince1970)),
"oauth_nonce": nonce,
"oauth_token": credentials.key,
].merged(with: parameters.filter(byKey: { $0.hasPrefix("oauth_") }))
}
public var authorizationHeader: String {
let headerComponents = authorizationParameters
.dictionaryByAdding("oauth_signature", value: self.oauthSignature)
.urlEncodedQueryPairs(using: self.dataEncoding)
.map({ pair in "\(pair.0)=\"\(pair.1)\"" })
.sorted()
.joined(separator: ", ")
return "OAuth " + headerComponents
}
var signingKey: String {
let tokenSecret = credentials.secret.urlEncodedString()
let encodedConsumerSecret = consumerCredentials.secret.urlEncodedString()
return "\(encodedConsumerSecret)&\(tokenSecret)"
}
var encodedParameterString: String {
return authorizationParameters
.merged(with: parameters)
.urlEncodedQueryString(using: dataEncoding)
.components(separatedBy: "&")
.sorted()
.joined(separator: "&")
.urlEncodedString()
}
var signatureBaseString: String {
return "\(method)&\(encodedURL)&\(encodedParameterString)"
}
var encodedURL: String {
return urlWithNoQueryParams.absoluteString.urlEncodedString()
}
public var oauthSignature: String {
do {
let hasher = CryptoHasher(method: .sha1, defaultKey: nil)
let bytes = try hasher.make(signatureBaseString.makeBytes(), key: signingKey.makeBytes())
return AvailBase64Encoder.shared.encode(bytes).string
} catch let error {
fatalError(String(describing: error))
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment