Adds service to create user and get token

multistore
Laurent 2 years ago
parent e1f004d80a
commit 92b35c7c0f
  1. 4
      LeStorage.xcodeproj/project.pbxproj
  2. 67
      LeStorage/Services.swift
  3. 2
      LeStorage/Store.swift
  4. 66
      LeStorage/Utils/KeychainStore.swift

@ -23,6 +23,7 @@
C4A47D6D2B71364600ADC637 /* ModelObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D6C2B71364600ADC637 /* ModelObject.swift */; };
C4A47D6F2B7154F600ADC637 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = C4A47D6E2B7154F600ADC637 /* README.md */; };
C4A47D812B7665AD00ADC637 /* Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D802B76658F00ADC637 /* Migration.swift */; };
C4A47D842B7B97F000ADC637 /* KeychainStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4A47D832B7B97F000ADC637 /* KeychainStore.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -53,6 +54,7 @@
C4A47D6C2B71364600ADC637 /* ModelObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelObject.swift; sourceTree = "<group>"; };
C4A47D6E2B7154F600ADC637 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; };
C4A47D802B76658F00ADC637 /* Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Migration.swift; sourceTree = "<group>"; };
C4A47D832B7B97F000ADC637 /* KeychainStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainStore.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -123,6 +125,7 @@
children = (
C4A47D502B6D2C4E00ADC637 /* Codable+Extensions.swift */,
C4A47D542B6D2DBF00ADC637 /* FileUtils.swift */,
C4A47D832B7B97F000ADC637 /* KeychainStore.swift */,
C4A47D522B6D2C5F00ADC637 /* Logger.swift */,
C4A47D6A2B71244100ADC637 /* Collection+Extension.swift */,
);
@ -248,6 +251,7 @@
buildActionMask = 2147483647;
files = (
C4A47D532B6D2C5F00ADC637 /* Logger.swift in Sources */,
C4A47D842B7B97F000ADC637 /* KeychainStore.swift in Sources */,
C4A47D512B6D2C4E00ADC637 /* Codable+Extensions.swift in Sources */,
C425D4392B6D24E1002A7B48 /* LeStorage.docc in Sources */,
C4A47D612B6D3C1300ADC637 /* Services.swift in Sources */,

@ -18,14 +18,25 @@ enum ServiceError: Error {
case urlCreationError(url: String)
}
class Services {
public class Services {
let keychainStore: KeychainStore
init(url: String) {
self.baseURL = url
self.keychainStore = KeychainStore(serverId: url)
}
fileprivate(set) var baseURL: String
fileprivate var jsonEncoder: JSONEncoder = {
let encoder = JSONEncoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
encoder.dateEncodingStrategy = .formatted(dateFormatter)
return encoder
}()
fileprivate var jsonDecoder: JSONDecoder = {
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
@ -94,7 +105,61 @@ class Services {
request.httpMethod = apiCall.method
request.httpBody = apiCall.body.data(using: .utf8)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
let token = try self.keychainStore.getToken()
request.setValue("Token \(token)", forHTTPHeaderField: "Authorization")
} catch {
Logger.log("missing token")
}
return request
}
// MARK: - Authentication
public func createAccount(username: String, password: String, email: String) async throws {
var postRequest = try self._baseRequest(servicePath: "users/", method: .post)
let user = User(username: username, password: password, email: email)
postRequest.httpBody = try jsonEncoder.encode(user)
let _: User = try await self.runRequest(postRequest)
let _ = try await requestToken(user: user)
}
func requestToken(user: User) async throws -> String {
var postRequest = try self._baseRequest(servicePath: "api-token-auth/", method: .post)
postRequest.httpBody = try jsonEncoder.encode(user)
let response: AuthResponse = try await self.runRequest(postRequest)
try self.keychainStore.add(username: user.username, token: response.token)
return response.token
}
func forgotPassword(user: User) async throws {
// var postRequest = try self._baseRequest(servicePath: "forgot-password/", method: .post)
// postRequest.httpBody = try jsonEncoder.encode(credentials)
// let response: User = try await self.runRequest(postRequest)
// let _ = try await requestToken(credentials: credentials)
// return response
}
}
struct AuthResponse: Codable {
let token: String
}
struct Credentials: Codable {
var username: String
var password: String
}
struct User: Codable {
var id: Int = 0
var username: String
var password: String?
var email: String?
}

@ -71,7 +71,7 @@ public class Store {
}
/// The service instance
var service: Services? {
public var service: Services? {
return self._services
}

@ -0,0 +1,66 @@
//
// KeychainStore.swift
// LeCountdown
//
// Created by Laurent Morvillier on 20/12/2023.
//
import Foundation
enum KeychainError: Error {
case noPassword
case unexpectedPasswordData
case unhandledError(status: OSStatus)
}
class KeychainStore {
let serverId: String
init(serverId: String) {
self.serverId = serverId
}
func add(username: String, token: String) throws {
let tokenData = token.data(using: .utf8)!
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: username,
kSecAttrServer as String: self.serverId,
kSecValueData as String: tokenData]
let status = SecItemAdd(query as CFDictionary, nil)
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
}
func getToken() throws -> String {
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrServer as String: self.serverId,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnAttributes as String: true,
kSecReturnData as String: true]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status != errSecItemNotFound else { throw KeychainError.noPassword }
guard status == errSecSuccess else { throw KeychainError.unhandledError(status: status) }
guard let existingItem = item as? [String : Any],
let tokenData = existingItem[kSecValueData as String] as? Data,
let token = String(data: tokenData, encoding: .utf8)
else {
throw KeychainError.unexpectedPasswordData
}
return token
}
func deleteToken() throws {
let query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrServer as String: self.serverId]
let status = SecItemDelete(query as CFDictionary)
guard status == errSecSuccess || status == errSecItemNotFound else { throw KeychainError.unhandledError(status: status) }
}
}
Loading…
Cancel
Save