Skip to content

Commit

Permalink
Fix/#72 (#78)
Browse files Browse the repository at this point in the history
* [#72] 회원가입 버그 수정

* [#72] 토큰 reissue 구현

* [#72] 회원가입 버그 수정
  • Loading branch information
kimsoomin20221789 authored Aug 2, 2024
1 parent 30faff6 commit 798f9cf
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 76 deletions.
94 changes: 56 additions & 38 deletions Spon-Us/Auth/AuthPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,56 +10,74 @@ import Moya

final class AuthPlugin: PluginType {
var onRetrySuccess: (() -> Void)?
var onRetryFail: (() -> Void)?

private var originalRequest: URLRequest?

func willSend(_ request: RequestType, target: TargetType) {
originalRequest = request.request
}

func didReceive(_ result: Result<Response, MoyaError>, target: TargetType) {
switch result {
case .success(let response):
if response.statusCode == 401 {
// 토큰이 만료된 경우 -> Reissue
TokenManager.shared.refreshAccessToken { success in
if success {
// 원래 요청 재시도.
guard var newRequest = response.request else {
print("🤢 원래 요청을 생성할 수 없습니다.")
return
}
if let newToken = TokenManager.shared.accessToken {
newRequest.setValue("Bearer \(newToken)", forHTTPHeaderField: "Authorization")
handle401Error(for: response)
}
case .failure(let error):
if error.response?.statusCode == 401 {
handle401Error(for: error.response!)
} else {
print("Request failed with error: \(error)")
}
}
}

private func handle401Error(for response: Response) {
// 토큰 재발급 시도
TokenManager.shared.refreshAccessToken { success in
if success {
// 원래 요청 재시도
guard var newRequest = self.originalRequest else {
print("🤢 원래 요청을 생성할 수 없습니다.")
self.onRetryFail?()
return
}
if let newToken = TokenManager.shared.accessToken {
newRequest.setValue("Bearer \(newToken)", forHTTPHeaderField: "Authorization")
}
let task = URLSession.shared.dataTask(with: newRequest) { data, response, error in
if let error = error {
print("🤢 재시도 요청 실패: \(error)")
DispatchQueue.main.async {
self.onRetryFail?()
}
let task = URLSession.shared.dataTask(with: newRequest) { data, response, error in
if let error = error {
print("🤢 재시도 요청 실패: \(error)")
} else if let response = response as? HTTPURLResponse {
if (200...299).contains(response.statusCode) {
print("😊 재시도 요청 성공: \(response.statusCode)")
DispatchQueue.main.async {
self.onRetrySuccess?()
}
} else {
print("🤢 재시도 요청 실패: \(response.statusCode)")
}
} else {
print("🤢 알 수 없는 오류 발생")
} else if let response = response as? HTTPURLResponse {
if (200...299).contains(response.statusCode) {
print("😊 재시도 요청 성공: \(response.statusCode)")
DispatchQueue.main.async {
self.onRetrySuccess?()
}
} else {
print("🤢 재시도 요청 실패: \(response.statusCode)")
DispatchQueue.main.async {
self.onRetryFail?()
}
}
task.resume()
} else {
print("🤢 refreshToken 만료!!")
print("🤢 알 수 없는 오류 발생")
DispatchQueue.main.async {
self.onRetryFail?()
}
}
}
task.resume()
} else {
print("🤢 refreshToken 만료!!")
DispatchQueue.main.async {
self.onRetryFail?()
}
}
case .failure(let error):
print("Request failed with error: \(error)")
}
}
}


// MARK: Plugin 사용법!
//let authPlugin = AuthPlugin()
// onRetrySuccess -> 작동 시킨 후 UI적으로 재시동시킬것
//authPlugin.onRetrySuccess = { [weak self] in
// self?.fetchReservationAPI(for: self?.selectedDate ?? Date())
// self?.fetchWeekReservationAPI(for: self?.selectedDate ?? Date())
//}
//self.provider = MoyaProvider<NewReservationAPI>(plugins: [authPlugin])
37 changes: 35 additions & 2 deletions Spon-Us/Auth/TokenManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
//

import Foundation
import Moya

class TokenManager {
static let shared = TokenManager()

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

private init() { }

var accessToken: String? {
Expand Down Expand Up @@ -53,11 +55,42 @@ class TokenManager {
UserDefaults.standard.removeObject(forKey: "refreshToken")
UserDefaults.standard.removeObject(forKey: "isAutoLogin")
UserDefaults.standard.removeObject(forKey: "fcmToken")
print("\(UserDefaults.standard.bool(forKey: "isAutoLogin"))")
}

// reissue API 붙이기
func refreshAccessToken(completion: @escaping (Bool) -> Void) {
guard let refreshToken = refreshToken else {
completion(false)
return
}

provider.request(.getReissue(refreshToken: refreshToken)) { result in
switch result {
case .success(let response):
if response.statusCode == 400 {
print("🚨토큰 재발급 네트워크 실패: 상태 코드 400 -> refresh 토큰 만료")
completion(false)
return
}
do {
if let loginResponse = try? response.map(LoginResponse.self) {
self.accessToken = loginResponse.content.accessToken
self.refreshToken = loginResponse.content.refreshToken
completion(true)
print("🚨reissue API 성공")
print(" access:\(loginResponse.content.accessToken)")
print(" refresh:\(loginResponse.content.refreshToken)")
}
} catch {
print("🚨reissue API 네트워크 매핑 실패")
completion(false)
}
case .failure(let error):
print("🚨reissue API 네트워크 실패")
print("refreshToken:\(refreshToken)")
completion(false)
}
}
}

// refreshToken 만료됐는지 확인하는 로직 붙이기
Expand Down
23 changes: 21 additions & 2 deletions Spon-Us/Home/HomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,27 @@ import Moya

@Observable
final class HomeViewModel {
let provider: MoyaProvider<SponusAPI> = .init()
private let authPlugin = AuthPlugin()
private var provider: MoyaProvider<SponusAPI>!

init() {
setupProvider()
setupAuthPluginCallbacks()
}

private func setupProvider() {
self.provider = MoyaProvider<SponusAPI>(plugins: [authPlugin])
}

private func setupAuthPluginCallbacks() {
authPlugin.onRetrySuccess = { [weak self] in
// 재시도 성공 시 필요한 동작 추가.
}
authPlugin.onRetryFail = { [weak self] in
// loginVM.logout()
}
}

var companies: [OrganizationModel] = []
var clubs: [OrganizationModel] = []

Expand Down Expand Up @@ -164,7 +183,7 @@ final class HomeViewModel {
case .all:
filteredClubs = clubs
case .advertisingAndMarketing:
filteredClubs = clubs.filter { $0.subTypes.contains("AD_MARKETING") }
filteredClubs = clubs.filter { $0.subTypes.contains("AD_MARKETING") }
case .design:
filteredClubs = clubs.filter { $0.subTypes.contains("DESIGN") }
case .iTAndSoftware:
Expand Down
22 changes: 22 additions & 0 deletions Spon-Us/Network/SponusAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ enum SponusAPI {
case postLogin(loginDetails: LoginRequest)
case deleteKeyword(keyword: String)
case deleteSearch
case getVerifyEmail(email: String)
case getReissue(refreshToken: String)
case getMyOrganization
case patchMyClubProfile(clubProfile: ClubProfile)
case postProfileImage(image: UIImage)
Expand Down Expand Up @@ -61,6 +63,10 @@ extension SponusAPI: TargetType {
return "/api/v2/organizations/join"
case .postLogin:
return "/api/v2/auth/login"
case .getVerifyEmail:
return "/api/v2/auth/verify-email"
case .getReissue:
return "/api/v2/auth/reissue"
case .getMyOrganization:
return "/api/v2/organizations/me"
case .patchMyClubProfile:
Expand Down Expand Up @@ -98,6 +104,10 @@ extension SponusAPI: TargetType {
return .post
case .postLogin:
return .post
case .getVerifyEmail:
return .get
case .getReissue:
return .get
case .getMyOrganization:
return .get
case .patchMyClubProfile:
Expand Down Expand Up @@ -135,6 +145,10 @@ extension SponusAPI: TargetType {
return Data()
case .postLogin:
return Data()
case .getVerifyEmail:
return Data()
case .getReissue:
return Data()
case .getMyOrganization:
return Data()
case .patchMyClubProfile:
Expand Down Expand Up @@ -178,6 +192,10 @@ extension SponusAPI: TargetType {
return .requestJSONEncodable(signUpDetails)
case .postLogin(loginDetails: let loginDetails):
return .requestJSONEncodable(loginDetails)
case .getVerifyEmail:
return .requestPlain
case .getReissue:
return .requestPlain
case .getMyOrganization:
return .requestPlain
case .patchMyClubProfile(let clubProfile):
Expand Down Expand Up @@ -226,6 +244,10 @@ extension SponusAPI: TargetType {
return ["Content-Type": "application/json"]
case .postLogin:
return ["Content-Type": "application/json"]
case .getVerifyEmail(email: let email):
return ["email": email]
case .getReissue(refreshToken: let refreshToken):
return ["RefreshToken": "\(refreshToken)"]
case .getMyOrganization:
return auth
case .patchMyClubProfile:
Expand Down
56 changes: 40 additions & 16 deletions Spon-Us/OnBoarding/View/SignUpIdView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct SignUpIdView: View {
@State private var id: String = ""
@State private var inputAuthNumber: String = ""
@State private var idValid: Bool = false
@State private var idExisted: Bool = false
@State private var idEmpty: Bool = true
@State private var isTimerRunning = false
@State private var timeRemaining = 0
Expand All @@ -36,7 +37,7 @@ struct SignUpIdView: View {

//MARK: Property
private let vm: SignUpViewModel = SignUpViewModel()
private let emailRegexPattern = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}$"
private let emailRegexPattern = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

//MARK: View
Expand All @@ -56,8 +57,16 @@ struct SignUpIdView: View {
.foregroundColor(.textPrimary)
.frame(maxWidth: .infinity)
.onChange(of: id) { _, _ in
idValid = isValidEmail(id)
idEmpty = {return id == ""}()
idValid = isValidEmail(id)
if idValid {
vm.isValidEmail(email: id) { status in
if let status = status {
print("💚status : \(status)")
idExisted = (status == "EXIST")
}
}
}
}
Button(action: {
id = ""
Expand All @@ -73,7 +82,7 @@ struct SignUpIdView: View {
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(
idEmpty ? Color.line200 : (!idValid ? .statusRed : Color.textBrand)
idEmpty ? Color.line200 : (!idValid || idExisted ? .statusRed : Color.textBrand)
)
)
.padding(.top, 20)
Expand All @@ -92,17 +101,32 @@ struct SignUpIdView: View {
} else {
if !afterCheckingEmail {
if idValid {
HStack() {
Image("Tick Square 3")
.frame(width: 13.33, height: 13.33)
.padding(.trailing, 1)
Text("사용 가능한 이메일이에요.")
.font(.B4KrMd)
.foregroundColor(.textBrand)
Spacer()
if idExisted {
HStack() {
Image("icDanger")
.frame(width: 13.33, height: 13.33)
.padding(.trailing, 1)
Text("이미 존재하는 이메일입니다.")
.font(.B4KrMd)
.foregroundColor(Color(red: 1, green: 0, blue: 0)
)
Spacer()
}
.padding(.top, 8)
.padding(.leading, 24)
} else {
HStack() {
Image("Tick Square 3")
.frame(width: 13.33, height: 13.33)
.padding(.trailing, 1)
Text("사용 가능한 이메일이에요.")
.font(.B4KrMd)
.foregroundColor(.textBrand)
Spacer()
}
.padding(.top, 8)
.padding(.leading, 24)
}
.padding(.top, 8)
.padding(.leading, 24)
} else {
HStack() {
Image("icDanger")
Expand All @@ -128,7 +152,7 @@ struct SignUpIdView: View {
vm.postEmail(email: id)
afterRequest = true
} else {
if (vm.code == inputAuthNumber) {
if (vm.code == inputAuthNumber && !inputAuthNumber.isEmpty) {
isEmailValidated = true
} else {
wrongAuthNumber = true
Expand Down Expand Up @@ -201,11 +225,11 @@ struct SignUpIdView: View {
.stroke(!idEmpty ? Color.textBrand : Color.line200)
)
}
.disabled(!idValid)
.disabled(!idValid || idExisted)
.padding(.horizontal, 20)
.padding(.bottom, 20)
} else {
NavigationLink(destination: SignUpPWView(vm: vm).navigationBarHidden(true)) {
NavigationLink(destination: SignUpPWView(vm: vm).navigationBarHidden(true).onAppear(perform: {vm.email = id})) {
Text("다음")
.font(.But1KrBd)
.frame(maxWidth: .infinity, maxHeight: 56)
Expand Down
2 changes: 1 addition & 1 deletion Spon-Us/OnBoarding/View/SignUpNameView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ struct SignUpNameView: View {
.background(Color.bgSecondary)

//MARK: Navigate
NavigationLink(destination: SuccessSignUpView().navigationBarHidden(true), isActive: $navigate) {
NavigationLink(destination: SuccessSignUpView(signUpVM: vm).navigationBarHidden(true), isActive: $navigate) {
EmptyView()
}
}
Expand Down
Loading

0 comments on commit 798f9cf

Please sign in to comment.