-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] 로그인 기능 구현 #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feat] 로그인 기능 구현 #86
Changes from all commits
1a7177b
6aa6746
b5a5af1
c76b16e
394115c
77a28d1
6398e34
217513c
6d47fd4
301608b
8dca417
21305dd
7122fcc
b0f06eb
ea6129c
b0ad9e5
db67f6c
9c1f085
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| <plist version="1.0"> | ||
| <dict> | ||
| <key>aps-environment</key> | ||
| <string>development</string> | ||
| <key>com.apple.developer.applesignin</key> | ||
| <array> | ||
| <string>Default</string> | ||
| </array> | ||
| </dict> | ||
| </plist> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| // | ||
| // AppleAuthManager.swift | ||
| // SniffMeet | ||
| // | ||
| // Created by sole on 5/2/25. | ||
| // | ||
|
|
||
| import AuthenticationServices | ||
|
|
||
| protocol AppleAuthManageable: AnyObject { | ||
| func signIn() async throws -> String | ||
| } | ||
|
|
||
| final class AppleAuthManager: NSObject, AppleAuthManageable { | ||
| private let provider: ASAuthorizationAppleIDProvider = ASAuthorizationAppleIDProvider() | ||
| private var authorizationController: ASAuthorizationController? | ||
| #if DEBUG | ||
| private var continuation: CheckedContinuation<String, Error>? | ||
| #else | ||
| private var continuation: UnsafeContinuation<String, Error>? | ||
| #endif | ||
|
|
||
| private override init() { | ||
| super.init() | ||
| } | ||
|
|
||
| func signIn() async throws -> String { | ||
| // 중복 로그인 요청 방지 및 continuation misuse/crash 방지 | ||
| guard continuation == nil else { throw AppleAuthError.signInFailed } | ||
| let request = provider.createRequest() | ||
| authorizationController = ASAuthorizationController(authorizationRequests: [request]) | ||
| authorizationController?.delegate = self | ||
| authorizationController?.presentationContextProvider = self | ||
| #if DEBUG | ||
| let idToken = try await withCheckedThrowingContinuation { [weak self] continuation in | ||
| self?.continuation = continuation | ||
| self?.authorizationController?.performRequests() | ||
| } | ||
| #else | ||
| let idToken = try await withUnsafeThrowingContinuation { [weak self] continuation in | ||
| self?.continuation = continuation | ||
| self?.authorizationController?.performRequests() | ||
| } | ||
| #endif | ||
| return idToken | ||
| } | ||
|
|
||
| private func resumeOnce(with result: Result<String, Error>) { | ||
| guard let continuation else { return } | ||
| self.continuation = nil | ||
| switch result { | ||
| case .success(let idToken): | ||
| continuation.resume(returning: idToken) | ||
| case .failure(let error): | ||
| continuation.resume(throwing: error) | ||
| } | ||
| } | ||
|
Comment on lines
+48
to
+57
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍👍 |
||
| } | ||
|
|
||
| // MARK: - AppleAuthManager+ | ||
|
|
||
| extension AppleAuthManager { | ||
| static let shared: AppleAuthManager = AppleAuthManager() | ||
| } | ||
|
|
||
| extension AppleAuthManager: ASAuthorizationControllerPresentationContextProviding { | ||
| func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { | ||
| if let windowScene = UIApplication.shared.keyWindow?.windowScene { | ||
| ASPresentationAnchor(windowScene: windowScene) | ||
| } else { | ||
| ASPresentationAnchor() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 정말 개인적으로 한 고민인데요. |
||
| } | ||
| } | ||
| } | ||
|
|
||
| extension AppleAuthManager: ASAuthorizationControllerDelegate { | ||
| func authorizationController( | ||
| controller: ASAuthorizationController, | ||
| didCompleteWithAuthorization authorization: ASAuthorization | ||
| ) { | ||
| guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential, | ||
| let idTokenData = credential.identityToken, | ||
| let idToken = String(data: idTokenData, encoding: .utf8) else { | ||
| resumeOnce(with: .failure(AppleAuthError.idTokenNotFound)) | ||
| return | ||
| } | ||
| resumeOnce(with: .success(idToken)) | ||
| } | ||
| func authorizationController( | ||
| controller: ASAuthorizationController, | ||
| didCompleteWithError error: any Error | ||
| ) { | ||
| resumeOnce(with: .failure(AppleAuthError.signInFailed)) | ||
| } | ||
| } | ||
|
|
||
| // MARK: - AppleAuthError | ||
|
|
||
| enum AppleAuthError: LocalizedError { | ||
| case idTokenNotFound | ||
| case signInFailed | ||
| case signInAlreadyInProgress | ||
|
|
||
| var localizedDescription: String { | ||
| switch self { | ||
| case .idTokenNotFound: "idToken을 찾을 수 없습니다." | ||
| case .signInFailed: "애플 로그인에 실패했습니다." | ||
| case .signInAlreadyInProgress: "애플 로그인 진행 중입니다." | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| // | ||
| // GoogleAuthManager.swift | ||
| // SniffMeet | ||
| // | ||
| // Created by sole on 5/2/25. | ||
| // | ||
|
|
||
| import GoogleSignIn | ||
|
|
||
| protocol GoogleAuthManageable { | ||
| @MainActor func signIn() async throws -> String | ||
| func signOut() | ||
| } | ||
|
|
||
| final class GoogleAuthManager: GoogleAuthManageable { | ||
| private init() {} | ||
|
|
||
| @MainActor | ||
| func signIn() async throws -> String { | ||
| guard let rootViewController = UIApplication.shared.keyWindow?.rootViewController else { | ||
| throw GoogleAuthError.viewHierarchyNotFound | ||
| } | ||
| do { | ||
| let result = try await GIDSignIn.sharedInstance.signIn( | ||
| withPresenting: rootViewController | ||
| ) | ||
| guard let idToken = result.user.idToken?.tokenString else { | ||
| throw GoogleAuthError.idTokenNotFound | ||
| } | ||
| return idToken | ||
| } catch let error as GoogleAuthError { | ||
| throw error | ||
| } catch { | ||
| throw GoogleAuthError.signInFailed | ||
| } | ||
| } | ||
|
|
||
| func signOut() { | ||
| GIDSignIn.sharedInstance.signOut() | ||
| } | ||
| } | ||
|
|
||
| extension GoogleAuthManager { | ||
| static let shared: GoogleAuthManageable = GoogleAuthManager() | ||
| } | ||
|
|
||
| enum GoogleAuthError: Error { | ||
| case viewHierarchyNotFound | ||
| case idTokenNotFound | ||
| case signInFailed | ||
|
|
||
| var localizedDescription: String { | ||
| switch self { | ||
| case .viewHierarchyNotFound: "로그인 창을 띄울 뷰 계층을 찾을 수 없습니다." | ||
| case .idTokenNotFound: "idToken을 찾을 수 없습니다." | ||
| case .signInFailed: "구글 로그인에 실패했습니다." | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P4.
아래에
signInAlreadyInProgress도 정의되어 있어서signInFailed로 하신 이유가 궁금합니다!