Skip to content

Commit

Permalink
[#73] 로그인 API연동 완료 | 자동로그인, 키체인 토큰 저장 구현중
Browse files Browse the repository at this point in the history
  • Loading branch information
00yhsp committed Feb 12, 2024
1 parent 1467f9a commit 80ce0d1
Show file tree
Hide file tree
Showing 5 changed files with 170 additions and 20 deletions.
4 changes: 4 additions & 0 deletions Spon-us.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
DF498F212B790BD000ADE078 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = DF498F202B790BCF00ADE078 /* GoogleService-Info.plist */; };
DF498F232B791EA800ADE078 /* LoginModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF498F222B791EA800ADE078 /* LoginModel.swift */; };
DF498F252B791EB400ADE078 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF498F242B791EB400ADE078 /* LoginViewModel.swift */; };
DF498F272B79B45D00ADE078 /* KeyChainManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF498F262B79B45D00ADE078 /* KeyChainManager.swift */; };
DF90A5AB2B64EB5500BC54D0 /* TermsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF90A5AA2B64EB5500BC54D0 /* TermsView.swift */; };
DF90A5AD2B664E5A00BC54D0 /* ProcessingPolicyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF90A5AC2B664E5A00BC54D0 /* ProcessingPolicyView.swift */; };
DF90A5B02B664E9600BC54D0 /* GatherAndUsageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF90A5AF2B664E9600BC54D0 /* GatherAndUsageView.swift */; };
Expand Down Expand Up @@ -174,6 +175,7 @@
DF498F202B790BCF00ADE078 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = "<group>"; };
DF498F222B791EA800ADE078 /* LoginModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginModel.swift; sourceTree = "<group>"; };
DF498F242B791EB400ADE078 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = "<group>"; };
DF498F262B79B45D00ADE078 /* KeyChainManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyChainManager.swift; sourceTree = "<group>"; };
DF90A5AA2B64EB5500BC54D0 /* TermsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsView.swift; sourceTree = "<group>"; };
DF90A5AC2B664E5A00BC54D0 /* ProcessingPolicyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProcessingPolicyView.swift; sourceTree = "<group>"; };
DF90A5AF2B664E9600BC54D0 /* GatherAndUsageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GatherAndUsageView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -443,6 +445,7 @@
3B81BCB32B622ED00067E9CB /* StoreKitTestView.swift */,
3B81BCBD2B62312C0067E9CB /* Product.storekit */,
3B81BCBF2B6231680067E9CB /* ProductList.plist */,
DF498F262B79B45D00ADE078 /* KeyChainManager.swift */,
);
path = Manager;
sourceTree = "<group>";
Expand Down Expand Up @@ -646,6 +649,7 @@
100A1E322B73485800AAC1E8 /* SponusAPI.swift in Sources */,
807BF83F2B51BCD400A659B9 /* SearchView.swift in Sources */,
DF90A5AB2B64EB5500BC54D0 /* TermsView.swift in Sources */,
DF498F272B79B45D00ADE078 /* KeyChainManager.swift in Sources */,
3B36F0A12B6FEBF60000ACFB /* Utils.swift in Sources */,
100D38A72B44836800498977 /* Spon_usApp.swift in Sources */,
100A1E382B734DB300AAC1E8 /* EmailModel.swift in Sources */,
Expand Down
124 changes: 124 additions & 0 deletions Spon-us/Manager/KeyChainManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// KeyChainManager.swift
// Spon-us
//
// Created by 박현수 on 2/12/24.
//

import Foundation

enum KeychainError: Error {
case itemNotFound
case duplicateItem
case invalidItemFormat
case unknown(OSStatus)
}

class KeychainManager {
static let service = Bundle.main.bundleIdentifier

static func save(account: String, value: String, isForce: Bool = false) throws {
try save(account: account, value: value.data(using: .utf8)!, isForce: isForce)
}

static func save(account: String, value: Data, isForce: Bool = false) throws {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword,
kSecValueData as String: value as AnyObject,
]

let status = SecItemAdd(query as CFDictionary, nil)

if status == errSecDuplicateItem {
if isForce {
try update(account: account, value: value)
return
} else {
throw KeychainError.duplicateItem
}
}

guard status == errSecSuccess else {
throw KeychainError.unknown(status)
}
}

// MARK: - Update

static func update(account: String, value: String) throws {
try update(account: account, value: value.data(using: .utf8)!)
}

static func update(account: String, value: Data) throws {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword,
kSecValueData as String: value as AnyObject,
]

let attributes: [String: AnyObject] = [
kSecValueData as String: value as AnyObject
]

let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)

guard status != errSecDuplicateItem else {
throw KeychainError.duplicateItem
}

guard status == errSecSuccess else {
throw KeychainError.unknown(status)
}
}

// MARK: - Load

static func load(account: String) throws -> String {
try String(decoding: load(account: account), as: UTF8.self)
}

static func load(account: String) throws -> Data {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword,
kSecMatchLimit as String: kSecMatchLimitOne,
kSecReturnData as String: kCFBooleanTrue,
]

var result: AnyObject?
let status = SecItemCopyMatching(query as CFDictionary, &result)
guard status != errSecItemNotFound else {
throw KeychainError.itemNotFound
}

guard status == errSecSuccess else {
throw KeychainError.unknown(status)
}

guard let password = result as? Data else {
throw KeychainError.invalidItemFormat
}

return password
}

// MARK: - Delete

static func delete(account: String) throws {
let query: [String: AnyObject] = [
kSecAttrService as String: service as AnyObject,
kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword
]

let status = SecItemDelete(query as CFDictionary)

guard status == errSecSuccess else {
throw KeychainError.unknown(status)
}
}
}
7 changes: 3 additions & 4 deletions Spon-us/Model/Onboarding/LoginModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,20 @@

import Foundation

struct LoginModelContent200: Codable {
struct LoginModelContent201: Codable {
let accessToken: String
let refreshToken: String
}

struct LoginModel200: Codable {
struct LoginModel201: Codable {
let statusCode: String
let message: String
let content: LoginModelContent200
let content: LoginModelContent201
}

struct LoginModel401: Codable {
let statusCode: String
let message: String
let content: String
}

struct LoginRequestBody: Codable {
Expand Down
15 changes: 10 additions & 5 deletions Spon-us/Model/Onboarding/LoginViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,28 @@ import Foundation
import Moya

class LoginViewModel: ObservableObject {
@Published var login200: LoginModel200?
@Published var login201: LoginModel201?
@Published var login401: LoginModel401?
@Published var isBadRequest = false

private let provider = MoyaProvider<SponusAPI>(plugins: [NetworkLoggerPlugin()])

func postLogin(email: String, password: String, fcmToken: String) {
func postLogin(email: String, password: String, fcmToken: String, completion: @escaping (Bool) -> Void) {
provider.request(.postLogin(email: email, password: password, fcmToken: fcmToken)) { result in
switch result {
case let .success(response):
do {
if response.statusCode == 200 {
let loginResponse = try response.map(LoginModel200.self)
self.login200 = loginResponse
if response.statusCode == 201 {
let loginResponse = try response.map(LoginModel201.self)
self.login201 = loginResponse
self.isBadRequest = false
completion(true)
}
else {
let loginResponse = try response.map(LoginModel401.self)
self.login401 = loginResponse
self.isBadRequest = true
completion(false)
}
} catch {
print("Error parsing response: \(error)")
Expand Down
40 changes: 29 additions & 11 deletions Spon-us/View/Onboarding/LoginView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@

import SwiftUI
import Firebase
import KeychainSwift

struct LoginView: View {
@ObservedObject var loginViewModel = LoginViewModel()

@State var userID = ""
@State private var userPW = ""
@State private var isPWSecure = true
@State var isEmailValid = false
@State var goToContentView = false
@State var goToTermsView = false
@State var disableButton = false

@FocusState private var isEmailTextFieldFocused: Bool
@FocusState private var isPWTextFieldFocused: Bool
Expand Down Expand Up @@ -89,37 +93,51 @@ struct LoginView: View {
}
}
}.padding(.bottom, 6)
if (isPWSecureFieldFocused || isPWTextFieldFocused) {
if loginViewModel.isBadRequest {
Divider()
.background(.sponusPrimary)
.background(.sponusRed).padding(.bottom, 8)
HStack(spacing: 0) {
Image(.icWarning).resizable().frame(width: 14, height: 14).padding(.trailing, 4)
Text("존재하지 않는 이메일 혹은 비밀번호입니다")
.font(.system(size: 12))
.foregroundStyle(.sponusRed)
Spacer()
}
}
// else if (((userID.wholeMatch(of: emailRegexPattern)?.output) != nil) || userID.isEmpty) {
// Divider()
// .background(.sponusGrey200)
// }
else {
Divider().background(.sponusGrey200)
Divider()
.background((isPWSecureFieldFocused || isPWTextFieldFocused) ? .sponusPrimary : .sponusGrey200)
}
}.padding(.horizontal, 20)
.padding(.bottom, 48)
Button {
disableButton = true
Messaging.messaging().token { token, error in
if let error = error {
print("Error fetching FCM registration token: \(error)")
} else if let token = token {
print("FCM registration token: \(token)")
// 여기서 토큰을 사용하거나 저장합니다.
loginViewModel.postLogin(email: userID, password: userPW, fcmToken: token) { success in
if success {
print("access token : \n\(String(describing: loginViewModel.login201?.content.accessToken))")
print("refresh token : \n\(String(describing: loginViewModel.login201?.content.refreshToken))")
goToContentView = true
}
else {
print("401\n\(String(describing: loginViewModel.login401?.message))")
}
}
}
}
//goToContentView = true
disableButton = false
} label: {
Text("로그인")
.font(.Body04).foregroundStyle(.sponusWhite)
.frame(maxWidth: .infinity).frame(height: 56)
.background(.sponusPrimary)
.background(disableButton ? .sponusGrey600 : .sponusPrimary)
.padding(.horizontal, 20)
.padding(.bottom, 20)
}.fullScreenCover(isPresented: $goToContentView, content: {
}.disabled(disableButton).fullScreenCover(isPresented: $goToContentView, content: {
ContentView()
})
Button {
Expand Down

0 comments on commit 80ce0d1

Please sign in to comment.