diff --git a/Api/Classes/AccessToken.swift b/Api/Classes/AccessToken.swift new file mode 100644 index 0000000000000000000000000000000000000000..2f55a9721177d47cc8091479efd5338def338a22 --- /dev/null +++ b/Api/Classes/AccessToken.swift @@ -0,0 +1,52 @@ +// +// AccessToken.swift +// Pods +// +// Created by Mauro Bender on 19/10/16. +// +// + +import Foundation + +public protocol AccessToken { + init (dictionary: [String: AnyObject]) throws + + var headerString: String { get } + var accessToken: String { get set } + var refreshToken: String? { get set } + var type: String { get set } + var expiresIn: Int { get set } + var createdAt: Date { get set } +} + +extension AccessToken { + var isExpired: Bool { get { return createdAt.addingTimeInterval(TimeInterval(expiresIn)) > Date() } } + var refreshCredentials: Credentials? { + get { + guard let refreshToken = refreshToken else { return nil } + return Credentials (refreshToken: refreshToken) + } + } +} + +struct GenricAccessToken: AccessToken { + public var type: String + public var accessToken: String + public var refreshToken: String? + public var expiresIn: Int + public var createdAt: Date + + public var headerString: String { return "Bearer \(accessToken)" } + + public init(dictionary: [String : AnyObject]) throws { + accessToken = dictionary ["access_token"] as! String + expiresIn = dictionary ["expires_in"] as! Int + type = dictionary ["token_type"] as! String + + if let refToken = dictionary ["refresh_token"] as? String { + refreshToken = refToken + } + + createdAt = Date() + } +} diff --git a/Api/Classes/Api.swift b/Api/Classes/Api.swift index 0025d386fa9f0ee2e507b2fcadf69dd0cde2b795..e2ad07740726d98ee93868854656b216b3138d02 100644 --- a/Api/Classes/Api.swift +++ b/Api/Classes/Api.swift @@ -15,10 +15,12 @@ open class Api { let apiPath: String var apiURL: URL { return baseURL.appendingPathComponent( apiPath ) } + var defaultHeaders: [String: String] public init( baseURL: URL, apiPath: String ) { self.baseURL = baseURL self.apiPath = apiPath + self.defaultHeaders = [String: String] () } // RESOURCE @@ -29,49 +31,62 @@ open class Api { // REQUESTS open func request( _ method: ApiMethod, path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default, - headers: [String: String]? = nil, completion: @escaping (DataResponse) -> Void ) + headers: [String: String]? = nil, appendPath: Bool = true, completion: @escaping (DataResponse) -> Void ) { - Alamofire.request( urlForPath( path ).absoluteString, method: method, parameters: parameters, - encoding: encoding, headers: headers ).responseJSON( completionHandler: completion ) + let requestHeaders = prepareRequestHeaders(headers: headers) + + Alamofire.request( urlForPath( path, appendPath: appendPath ).absoluteString, method: method, parameters: parameters, + encoding: encoding, headers: requestHeaders ).responseJSON( completionHandler: completion ) } open func get( _ path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default, - headers: [String: String]? = nil, completion: @escaping (DataResponse) -> Void ) + headers: [String: String]? = nil, appendPath: Bool = true, completion: @escaping (DataResponse) -> Void ) { return request( .get, path: path, parameters: parameters, encoding: encoding, - headers: headers, completion: completion) + headers: headers, appendPath: appendPath, completion: completion) } open func post( _ path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default, - headers: [String: String]? = nil, completion: @escaping (DataResponse) -> Void ) + headers: [String: String]? = nil, appendPath: Bool = true, completion: @escaping (DataResponse) -> Void ) { return request( .post, path: path, parameters: parameters, encoding: encoding, - headers: headers, completion: completion) + headers: headers, appendPath: appendPath, completion: completion) } open func put( _ path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default, - headers: [String: String]? = nil, completion: @escaping (DataResponse) -> Void ) + headers: [String: String]? = nil, appendPath: Bool = true, completion: @escaping (DataResponse) -> Void ) { return request( .put, path: path, parameters: parameters, encoding: encoding, - headers: headers, completion: completion) + headers: headers, appendPath: appendPath, completion: completion) } open func patch( _ path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default, - headers: [String: String]? = nil, completion: @escaping (DataResponse) -> Void ) + headers: [String: String]? = nil, appendPath: Bool = true, completion: @escaping (DataResponse) -> Void ) { return request( .patch, path: path, parameters: parameters, encoding: encoding, - headers: headers, completion: completion) + headers: headers, appendPath: appendPath, completion: completion) } open func delete( _ path: String, parameters: [String: AnyObject]? = nil, encoding: ParameterEncoding = URLEncoding.default, - headers: [String: String]? = nil, completion: @escaping (DataResponse) -> Void ) + headers: [String: String]? = nil, appendPath: Bool = true, completion: @escaping (DataResponse) -> Void ) { return request( .delete, path: path, parameters: parameters, encoding: encoding, - headers: headers, completion: completion) + headers: headers, appendPath: appendPath, completion: completion) } - func urlForPath( _ path: String ) -> URL { - return apiURL.appendingPathComponent( path ) + func prepareRequestHeaders(headers: [String: String]?) -> [String: String] { + guard let headers = headers else { return defaultHeaders } + + var requestHeaders = [String: String]() + requestHeaders += defaultHeaders + requestHeaders += headers + + return requestHeaders + } + + func urlForPath( _ path: String, appendPath: Bool = true ) -> URL { + let baseURL = appendPath ? self.apiURL : self.baseURL; + return baseURL.appendingPathComponent( path ) } } diff --git a/Api/Classes/Authenticator.swift b/Api/Classes/Authenticator.swift new file mode 100644 index 0000000000000000000000000000000000000000..4f912cb3e75e246ca1b9780beca576d692d39c70 --- /dev/null +++ b/Api/Classes/Authenticator.swift @@ -0,0 +1,66 @@ +// +// Authenticator.swift +// Pods +// +// Created by Mauro Bender on 19/10/16. +// +// + +import Foundation + +public enum AuthError: Error { + case apiError (apiError: ApiError) + case invalidRefreshToken () + case invalidToken () +} + +public enum AuthResult { + case success(accessToken: T) + case failure(error: AuthError) +} + +class Authenticator { + var api: Api + var authPath: String + + init (api: Api, authPath: String) { + self.api = api + self.authPath = authPath + } + + open func authenticate (credentials: Credentials, callback: @escaping ((_ result: AuthResult) -> Void)) { + let credentialsData = credentials.serialize() as [String: AnyObject] + api.post (authPath, parameters: credentialsData, appendPath: false) { response in + switch response.result { + case .success(let data): + if let data = data as? [String: AnyObject], let accessToken = try? T(dictionary: data) { + self.api.defaultHeaders ["Authorization"] = accessToken.headerString + callback(.success(accessToken: accessToken)) + } else { + callback(.failure(error: .invalidToken())) + } + break; + case .failure(_): + callback(.failure(error: .apiError(apiError: ApiError(response: response)))) + break; + } + } + } + + open func refresh (accessToken: T, callback: @escaping ((_ result: AuthResult) -> Void)) { + guard let refreshCredentials = accessToken.refreshCredentials else { + callback(.failure(error: .invalidRefreshToken())) + return + } + + authenticate(credentials: refreshCredentials, callback: callback) + } +} + + + + + + + + diff --git a/Api/Classes/Credentials.swift b/Api/Classes/Credentials.swift new file mode 100644 index 0000000000000000000000000000000000000000..7348d2ff9238eea3d897701692ccf6c371af10e4 --- /dev/null +++ b/Api/Classes/Credentials.swift @@ -0,0 +1,60 @@ +// +// Credentials.swift +// Pods +// +// Created by Mauro Bender on 25/8/16. +// +// + +struct Credentials { + let grantType: String + let data: [ String: String ] + + func serialize() -> [ String: String ] { + var data = self.data + data += [ "grant_type": grantType ] + + return data + } +} + +extension Credentials { + init( username: String, password: String, extra: [ String: String ]? = nil ) { + self.grantType = "password" + + var data = [ "username": username, "password": password ] + if extra != nil { data += extra } + + self.data = data + } + + init( assertion: String, extra: [ String: String ]? = nil ) { + self.grantType = "assertion" + + var data = [ "assertion": assertion ] + if extra != nil { data += extra } + + self.data = data + } + + init( refreshToken: String, extra: [ String: String ]? = nil ) { + self.grantType = "refresh_token" + + var data = [ "refresh_token": refreshToken ] + if extra != nil { data += extra } + + self.data = data + } + + public static func password( username: String, password: String, extra: [ String: String ]? = nil ) -> Credentials { + return Credentials( username: username, password: password, extra: extra ) + } + + public static func facebook( token: String, extra: [ String: String ]? = nil ) -> Credentials { + return Credentials( assertion: token, extra: extra ) + } + + public static func refreshToken( refreshToken: String, extra: [ String: String ]? = nil ) -> Credentials { + return Credentials( refreshToken: refreshToken, extra: extra ) + } +} diff --git a/Api/Classes/Utils.swift b/Api/Classes/Utils.swift index 1ed3d082cebd74f1b1cfc238a44610194814cf98..c34269da00b92467ed02d23b99faa96c8844d077 100644 --- a/Api/Classes/Utils.swift +++ b/Api/Classes/Utils.swift @@ -8,7 +8,9 @@ import Foundation -func += (left: inout [K:V], right: [K:V]) { +func += (left: inout [K:V], right: [K:V]?) { + guard let right = right else { return } + for (k, v) in right { left.updateValue(v, forKey: k) }