Put the keychainStore inside StoreCenter + authentication refactoring

sync2
Laurent 8 months ago
parent 6748c825df
commit 26efbdd27d
  1. 92
      LeStorage/Services.swift
  2. 9
      LeStorage/Store.swift
  3. 51
      LeStorage/StoreCenter.swift

@ -43,13 +43,8 @@ public class Services {
/// The base API URL to send requests
fileprivate(set) var baseURL: String
/// A KeychainStore object used to store the user's token
let keychainStore: KeychainStore
public init(url: String) {
self.baseURL = url
self.keychainStore = KeychainStore(serverId: url)
Logger.log("create keystore with id: \(url)")
}
static let storeIdURLParameter = "store_id"
@ -228,14 +223,14 @@ public class Services {
// let requiresToken = self._isTokenRequired(type: T.self, method: .delete)
// return try self._baseRequest(servicePath: T.path(id: id), method: .delete, requiresToken: requiresToken)
// }
/// Returns the base URLRequest for a ServiceConf instance
/// - Parameters:
/// - conf: a ServiceConf instance
fileprivate func _baseRequest(call: ServiceCall) throws -> URLRequest {
return try self._baseRequest(servicePath: call.path, method: call.method, requiresToken: call.requiresToken)
}
/// Returns the base URLRequest for a ServiceConf instance
/// - Parameters:
/// - conf: a ServiceConf instance
fileprivate func _baseRequest(call: ServiceCall) throws -> URLRequest {
return try self._baseRequest(servicePath: call.path, method: call.method, requiresToken: call.requiresToken)
}
/// Returns a base request for a path and method
/// - Parameters:
/// - servicePath: the path to add to the API base URL
@ -262,7 +257,7 @@ public class Services {
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.addAppVersion()
if !(requiresToken == false) {
let token = try self.keychainStore.getValue()
let token = try StoreCenter.main.token()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
}
return request
@ -270,28 +265,6 @@ public class Services {
// MARK: - Synchronization
/// Returns a base request for a path and method
/// - Parameters:
/// - method: the HTTP method to execute
/// - payload: the content to put in the httpBody
// fileprivate func _baseSyncRequest(method: HTTPMethod, payload: Encodable) throws -> URLRequest {
// let urlString = baseURL + "data/"
//
// guard let url = URL(string: urlString) else {
// throw ServiceError.urlCreationError(url: urlString)
// }
//
// var request = URLRequest(url: url)
// request.httpMethod = method.rawValue
// request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// request.httpBody = try JSON.encoder.encode(payload)
//
// let token = try self.keychainStore.getValue()
// request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
//
// return request
// }
/// Runs a request using a traditional URLRequest
/// - Parameters:
/// - request: the URLRequest to run
@ -379,7 +352,7 @@ public class Services {
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
if self._isTokenRequired(type: T.self, method: apiCall.method), StoreCenter.main.isAuthenticated {
let token = try self.keychainStore.getValue()
let token = try StoreCenter.main.token()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
}
@ -421,7 +394,7 @@ public class Services {
var request = URLRequest(url: url)
request.httpMethod = HTTPMethod.post.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let token = try self.keychainStore.getValue()
let token = try StoreCenter.main.token()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
let modelName = String(describing: T.self)
@ -471,7 +444,7 @@ public class Services {
request.httpMethod = HTTPMethod.get.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let token = try self.keychainStore.getValue()
let token = try StoreCenter.main.token()
request.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
return request
@ -564,7 +537,7 @@ public class Services {
request.addAppVersion()
if self._isTokenRequired(type: T.self, method: apiCall.method) {
do {
let token = try self.keychainStore.getValue()
let token = try StoreCenter.main.token()
request.setValue("Token \(token)", forHTTPHeaderField: "Authorization")
} catch {
Logger.log("missing token")
@ -613,28 +586,16 @@ public class Services {
let credentials = Credentials(username: username, password: password, deviceId: deviceId)
postRequest.httpBody = try JSON.encoder.encode(credentials)
let response: AuthResponse = try await self._runRequest(postRequest)
self._storeToken(username: username, token: response.token)
try StoreCenter.main.storeToken(username: username, token: response.token)
return response.token
}
/// Stores a token for a corresponding username
/// - Parameters:
/// - username: the key used to store the token
/// - token: the token to store
fileprivate func _storeToken(username: String, token: String) {
do {
try self.keychainStore.deleteValue()
try self.keychainStore.add(username: username, value: token)
} catch {
Logger.error(error)
}
}
/// A login method that actually requests a token from the server, and stores the appropriate data for later usage
/// - Parameters:
/// - username: the account's username
/// - password: the account's password
public func login<U: UserBase>(username: String, password: String) async throws -> U {
_ = try await requestToken(username: username, password: password)
let postRequest = try self._baseRequest(call: getUserCall)
let loggingDate = Date() // ideally we want the date of the latest retrieved object when loading collection objects
@ -681,7 +642,7 @@ public class Services {
async throws
{
guard let username = StoreCenter.main.userName() else {
guard let username = StoreCenter.main.userName else {
throw ServiceError.missingUserName
}
@ -696,7 +657,7 @@ public class Services {
let response: Token = try await self._runRequest(
serviceCall: changePasswordCall, payload: params)
self._storeToken(username: username, token: response.token)
try StoreCenter.main.storeToken(username: username, token: response.token)
}
/// The method send a request to reset the user's password
@ -724,21 +685,6 @@ public class Services {
let _: Empty = try await self._runRequest(request)
}
/// Deletes the locally stored token
func deleteToken() throws {
try self.keychainStore.deleteValue()
}
/// Returns whether the Service has an associated token
public func hasToken() -> Bool {
do {
_ = try self.keychainStore.getValue()
return true
} catch {
return false
}
}
/// Parse a json data and tries to extract its error message
/// - Parameters:
/// - data: some JSON data
@ -763,10 +709,6 @@ public class Services {
return nil
}
func migrateToken(_ services: Services, userName: String) throws {
try self._storeToken(username: userName, token: services.keychainStore.getValue())
}
// MARK: - Convenience method for tests
/// Executes a POST request

@ -11,6 +11,9 @@ import UIKit
public enum StoreError: Error, LocalizedError {
case missingService
case missingUserId
case missingUsername
case missingToken
case missingKeychainStore
case collectionNotRegistered(type: String)
case cannotSyncCollection(name: String)
case apiCallCollectionNotRegistered(type: String)
@ -19,8 +22,14 @@ public enum StoreError: Error, LocalizedError {
switch self {
case .missingService:
return "Services instance is nil"
case .missingUsername:
return "The username is missing"
case .missingUserId:
return "The user id is missing"
case .missingToken:
return "There is no stored token"
case .missingKeychainStore:
return "There is no keychain store"
case .collectionNotRegistered(let type):
return "The collection \(type) is not registered"
case .cannotSyncCollection(let name):

@ -16,6 +16,9 @@ public class StoreCenter {
/// A dictionary of Stores associated to their id
fileprivate var _stores: [String: Store] = [:]
/// A KeychainStore object used to store the user's token
var keychainStore: KeychainStore? = nil
/// Indicates to Stored Collection if they can synchronize
public var collectionsCanSynchronize: Bool = true
@ -73,6 +76,8 @@ public class StoreCenter {
let urlManager: URLManager = URLManager(secureScheme: secureScheme, domain: domain)
self._urlManager = urlManager
self._services = Services(url: urlManager.api)
self.keychainStore = KeychainStore(serverId: urlManager.api)
self._dataAccess = Store.main.registerSynchronizedCollection()
Logger.log("Sync URL: \(urlManager.api)")
@ -182,11 +187,6 @@ public class StoreCenter {
func userDidLogIn(user: UserBase, at date: Date) {
self._settingsStorage.update { settings in
settings.userId = user.id
settings.username = user.username
// let date = Date.microSecondFormatter.string(from: date)
// Logger.log("LOG date = \(date)")
settings.lastSynchronization = Date.microSecondFormatter.string(from: date)
self._configureWebSocket()
}
@ -198,18 +198,20 @@ public class StoreCenter {
}
/// Returns the username
public func userName() -> String? {
public var userName: String? {
return self._settingsStorage.item.username
}
/// Returns the stored token
public func token() -> String? {
return try? self.service().keychainStore.getValue()
public func token() throws -> String {
guard self.userName != nil else { throw StoreError.missingUsername }
guard let keychainStore else { throw StoreError.missingKeychainStore }
return try keychainStore.getValue()
}
/// Disconnect the user from the storage and resets collection
public func disconnect() {
try? self.service().deleteToken()
try? self.keychainStore?.deleteValue()
self.resetApiCalls()
self._failedAPICallsCollection?.reset()
@ -229,15 +231,26 @@ public class StoreCenter {
/// Returns whether the system has a user token
public var isAuthenticated: Bool {
guard self.userId != nil else { return false }
guard self.userName != nil else { return false }
do {
_ = try self.service().keychainStore.getValue()
let _ = try self.token()
return true
} catch {
return false
}
}
/// Stores a token for a corresponding username
/// - Parameters:
/// - username: the key used to store the token
/// - token: the token to store
func storeToken(username: String, token: String) throws {
self._settingsStorage.item.username = username
guard let keychainStore else { throw StoreError.missingKeychainStore }
try keychainStore.deleteValue()
try keychainStore.add(username: username, value: token)
}
/// Returns a generated device id
/// If created, stores it inside the keychain to get a consistent value even if the app is deleted
/// as UIDevice.current.identifierForVendor value changes when the app is deleted and installed again
@ -882,9 +895,7 @@ public class StoreCenter {
/// Returns whether the current userName is allowed to sync with the server
func userIsAllowed() -> Bool {
guard let userName = self.userName() else {
return true
}
guard let userName else { return true }
return !self._blackListedUserName.contains(where: { $0 == userName })
}
@ -1003,12 +1014,12 @@ public class StoreCenter {
// MARK: - Migration
/// Migrates the token from the provided service to the main Services instance
public func migrateToken(_ services: Services) throws {
guard let userName = self.userName() else {
return
}
try self.service().migrateToken(services, userName: userName)
}
// public func migrateToken(_ services: Services) throws {
// guard let userName = self.userName() else {
// return
// }
// try self.service().migrateToken(services, userName: userName)
// }
deinit {
NotificationCenter.default.removeObserver(self)

Loading…
Cancel
Save