Commit 1a037220 authored by Jose Blaya's avatar Jose Blaya
Browse files

Store the token in the Keychain (WIP)

parent 650573e8
......@@ -22,6 +22,9 @@ public protocol AccountProvider: class {
/// The user account currently logged in, or `nil` if logged out.
var currentUser: UserAccount? { get set }
/// The current auth token, or 'nil' if logged out.
var token: String? { get }
/// The password reference object associated with the currentUser, or `nil` if logged out.
var currentPasswordReference: Data? { get }
......
......@@ -19,5 +19,13 @@ protocol SecureStore: class {
func passwordReference(for username: String) -> Data?
func token(for username: String) -> String?
func setToken(_ token: String?, for username: String)
func tokenReference(for username: String) -> Data?
func tokenKey(for username: String) -> String
func clear(for username: String)
}
......@@ -75,6 +75,14 @@ class DefaultAccountProvider: AccountProvider, ConfigurationAccess, DatabaseAcce
}
}
var token: String? {
guard let username = accessedDatabase.plain.username else {
return nil
}
return accessedDatabase.secure.token(for: accessedDatabase.secure.tokenKey(for: username))
}
var currentPasswordReference: Data? {
guard let username = accessedDatabase.plain.username else {
return nil
......@@ -103,6 +111,10 @@ class DefaultAccountProvider: AccountProvider, ConfigurationAccess, DatabaseAcce
return
}
self.accessedDatabase.plain.username = request.credentials.username
self.accessedDatabase.secure.setToken(request.credentials.password,
for: self.accessedDatabase.secure.tokenKey(for: request.credentials.username))
self.webServices.info(token: token) { (accountInfo, error) in
guard let accountInfo = accountInfo else {
callback?(nil, error)
......@@ -110,10 +122,9 @@ class DefaultAccountProvider: AccountProvider, ConfigurationAccess, DatabaseAcce
}
//Save after confirm the login was successful.
self.accessedDatabase.plain.username = request.credentials.username
self.accessedDatabase.secure.setPassword(request.credentials.password, for: request.credentials.username)
self.accessedDatabase.secure.setPassword(request.credentials.password,
for: request.credentials.username)
self.accessedDatabase.plain.accountInfo = accountInfo
//TODO: PLEASE SAVE THE TOKEN HERE
let user = UserAccount(credentials: request.credentials, info: accountInfo)
Macros.postNotification(.PIAAccountDidLogin, [
......
......@@ -58,6 +58,31 @@ class KeychainStore: SecureStore {
func clear(for username: String) {
backend.removePassword(for: username)
backend.removeToken(for: tokenKey(for: username))
backend.remove(publicKeyWithIdentifier: Entries.publicKey)
}
}
extension KeychainStore {
func token(for username: String) -> String? {
return try? backend.token(for: username)
}
func setToken(_ token: String?, for username: String) {
if let token = token {
try? backend.set(token: token, for: username)
} else {
backend.removeToken(for: username)
}
}
func tokenReference(for username: String) -> Data? {
return try? backend.tokenReference(for: username)
}
func tokenKey(for username: String) -> String {
return "auth-token: \(username)"
}
}
......@@ -53,7 +53,7 @@ class PIAWebServices: WebServices, ConfigurationAccess {
429: .throttled
]
req(nil, .get, endpoint, nil, status, JSONRequestExecutor() { (json, status, error) in
req(nil, .get, endpoint, useAuthToken: true, nil, status, JSONRequestExecutor() { (json, status, error) in
if let knownError = self.knownError(endpoint, status, errors) {
callback?(nil, knownError)
return
......@@ -239,11 +239,11 @@ class PIAWebServices: WebServices, ConfigurationAccess {
headers[authHeader.key] = authHeader.value
}
if useToken {
//TODO: PLEASE GET TOKEN HERE
headers["Authorization"] = "Token PLEASE THE TOKEN HERE"
if useToken,
let token = Client.providers.accountProvider.token {
headers["Authorization"] = "Token \(token)"
}
if let parameters = parameters {
log.debug("Request: \(method) \"\(url)\", parameters: \(parameters), headers: \(headers)")
} else {
......
......@@ -10,6 +10,7 @@ import Foundation
/// Simulates account-related operations
public class MockAccountProvider: AccountProvider, WebServicesConsumer {
/// Mocks the outcome of a sign-up operation.
///
......@@ -111,6 +112,10 @@ public class MockAccountProvider: AccountProvider, WebServicesConsumer {
return delegate.isLoggedIn
}
public var token: String? {
return "TOKEN"
}
/// :nodoc:
public var currentUser: UserAccount? {
get {
......
......@@ -326,6 +326,8 @@ class EphemeralAccountProvider: AccountProvider, ProvidersAccess, InAppAccess {
var isLoggedIn = false
var token: String?
var currentUser: UserAccount?
var currentPasswordReference: Data? {
......
......@@ -226,3 +226,103 @@ public class Keychain {
}
}
}
extension Keychain {
// MARK: Token
/// :nodoc:
public func set(token: String, for username: String) throws {
removeToken(for: username)
var query = [String: Any]()
setScope(query: &query)
query[kSecClass as String] = kSecClassGenericPassword
query[kSecAttrAccount as String] = username
query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
query[kSecValueData as String] = token.data(using: .utf8)
let status = SecItemAdd(query as CFDictionary, nil)
guard (status == errSecSuccess) else {
throw KeychainError.add
}
}
/// :nodoc:
@discardableResult public func removeToken(for username: String) -> Bool {
var query = [String: Any]()
setScope(query: &query)
query[kSecClass as String] = kSecClassGenericPassword
query[kSecAttrAccount as String] = username
let status = SecItemDelete(query as CFDictionary)
return (status == errSecSuccess)
}
/// :nodoc:
public func token(for username: String) throws -> String {
var query = [String: Any]()
setScope(query: &query)
query[kSecClass as String] = kSecClassGenericPassword
query[kSecAttrAccount as String] = username
//query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
query[kSecMatchLimit as String] = kSecMatchLimitOne
query[kSecReturnData as String] = true
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard (status == errSecSuccess) else {
throw KeychainError.notFound
}
guard let data = result as? Data else {
throw KeychainError.notFound
}
guard let token = String(data: data, encoding: .utf8) else {
throw KeychainError.notFound
}
return token
}
/// :nodoc:
public func tokenReference(for username: String) throws -> Data {
var query = [String: Any]()
query[kSecClass as String] = kSecClassGenericPassword
setScope(query: &query)
query[kSecAttrAccount as String] = username
query[kSecMatchLimit as String] = kSecMatchLimitOne
query[kSecReturnPersistentRef as String] = true
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard (status == errSecSuccess) else {
throw KeychainError.notFound
}
guard let data = result as? Data else {
throw KeychainError.notFound
}
return data
}
/// :nodoc:
public static func token(for username: String, reference: Data) throws -> String {
var query = [String: Any]()
query[kSecClass as String] = kSecClassGenericPassword
query[kSecAttrAccount as String] = username
query[kSecMatchItemList as String] = [reference]
query[kSecReturnData as String] = true
var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard (status == errSecSuccess) else {
throw KeychainError.notFound
}
guard let data = result as? Data else {
throw KeychainError.notFound
}
guard let password = String(data: data, encoding: .utf8) else {
throw KeychainError.notFound
}
return password
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment