Skip to content

Commit 52b5f01

Browse files
authored
Merge pull request #246 from boostcampwm-2022/feature/refactoring-apple-signIn
Apple SignIn Proxy로 리팩토링
2 parents f5c407f + 480dedc commit 52b5f01

File tree

3 files changed

+113
-55
lines changed

3 files changed

+113
-55
lines changed

Trinap/Sources/Presenter/Auth/SignIn/SignInViewController.swift

Lines changed: 24 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ final class SignInViewController: BaseViewController {
3939
}
4040

4141
// MARK: - Properties
42-
private var currentNonce: String?
4342
private let viewModel: SignInViewModel
44-
private let credentialSub = PublishSubject<(OAuthCredential, String)>()
4543

4644
// MARK: - Initializers
4745
init(viewModel: SignInViewModel) {
@@ -82,22 +80,23 @@ final class SignInViewController: BaseViewController {
8280
make.centerX.equalToSuperview()
8381
}
8482
}
85-
83+
8684
override func bind() {
85+
let credential = appleSignInButton.rx.tap
86+
.asObservable()
87+
.flatMap {
88+
ASAuthorizationAppleIDProvider().rx.login(scope: [.email])
89+
}
90+
.withUnretained(self)
91+
.compactMap { owner, authorization in
92+
return owner.generateOAuthCredential(authorization: authorization)
93+
}
94+
8795
let input = SignInViewModel.Input(
88-
signInButtonTap: appleSignInButton.rx.tap.asObservable(),
89-
credential: credentialSub.asObservable()
96+
credential: credential
9097
)
9198
let output = viewModel.transform(input: input)
9299

93-
94-
output.presentSignInWithApple
95-
.asObservable()
96-
.subscribe { _ in
97-
self.startSignInWithAppleFlow()
98-
}
99-
.disposed(by: disposeBag)
100-
101100
privacyPolicyButton.rx.tap
102101
.bind(onNext: { [weak self] _ in
103102
self?.openPrivacyPolicy()
@@ -106,62 +105,35 @@ final class SignInViewController: BaseViewController {
106105
}
107106
}
108107

109-
// MARK: - ASAuthorizationControllerDelegate
110-
extension SignInViewController: ASAuthorizationControllerDelegate {
111-
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
108+
// MARK: - private Function
109+
private extension SignInViewController {
110+
111+
private func generateOAuthCredential(authorization: ASAuthorization) -> (OAuthCredential, String)? {
112112
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
113-
guard let nonce = currentNonce else {
114-
fatalError("Invalid state: A login callback was received, but no login request was sent.")
115-
}
113+
let nonce = generateRandomNonce().toSha256()
114+
116115
guard let appleIDToken = appleIDCredential.identityToken else {
117116
Logger.print("Unable to fetch identity token")
118-
return
117+
return nil
119118
}
120119
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
121120
Logger.print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
122-
return
121+
return nil
123122
}
124123

125124
guard
126125
let authorizationCode = appleIDCredential.authorizationCode,
127126
let codeString = String(data: authorizationCode, encoding: .utf8)
128127
else {
129128
Logger.print("Unable to serialize token string from authorizationCode")
130-
return
129+
return nil
131130
}
132131

133132
let credential = OAuthProvider.credential(withProviderID: "apple.com", idToken: idTokenString, rawNonce: nonce)
134-
credentialSub.onNext((credential, codeString))
133+
134+
return (credential, codeString)
135135
}
136-
}
137-
}
138-
139-
// MARK: - ASAuthorizationControllerPresentationContextProviding
140-
extension SignInViewController: ASAuthorizationControllerPresentationContextProviding {
141-
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
142-
return self.view.window ?? ASPresentationAnchor()
143-
}
144-
}
145-
146-
// MARK: - private Function
147-
private extension SignInViewController {
148-
func startSignInWithAppleFlow() {
149-
let (request, nonce) = createRequest()
150-
self.currentNonce = nonce
151-
152-
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
153-
authorizationController.delegate = self
154-
authorizationController.presentationContextProvider = self
155-
authorizationController.performRequests()
156-
}
157-
158-
func createRequest() -> (ASAuthorizationAppleIDRequest, String) {
159-
let nonce = self.generateRandomNonce()
160-
let request = ASAuthorizationAppleIDProvider().createRequest()
161-
request.requestedScopes = [.fullName, .email]
162-
request.nonce = nonce.toSha256()
163-
164-
return (request, nonce)
136+
return nil
165137
}
166138

167139
// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce

Trinap/Sources/Presenter/Auth/SignIn/SignInViewModel.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,10 @@ import RxSwift
1616
final class SignInViewModel: ViewModelType {
1717

1818
struct Input {
19-
let signInButtonTap: Observable<Void>
2019
let credential: Observable<(OAuthCredential, String)>
2120
}
2221

2322
struct Output {
24-
let presentSignInWithApple: Signal<Void>
2523
let signInFailure: Signal<Void>
2624
}
2725

@@ -65,7 +63,6 @@ final class SignInViewModel: ViewModelType {
6563
.disposed(by: disposeBag)
6664

6765
return Output(
68-
presentSignInWithApple: input.signInButtonTap.asSignal(onErrorSignalWith: .empty()),
6966
signInFailure: signInFailure.asSignal()
7067
)
7168
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//
2+
// ASAuthorizationController+Rx.swift
3+
// Trinap
4+
//
5+
// Created by ByeongJu Yu on 2022/12/27.
6+
// Copyright © 2022 Trinap. All rights reserved.
7+
//
8+
9+
import AuthenticationServices
10+
import RxSwift
11+
import RxCocoa
12+
13+
class ASAuthorizationControllerProxy: DelegateProxy<ASAuthorizationController, ASAuthorizationControllerDelegate>, DelegateProxyType {
14+
15+
// MARK: - Properties
16+
private var presentationWindow = UIWindow()
17+
internal lazy var didComplete = PublishSubject<ASAuthorization>()
18+
19+
20+
// MARK: - Initializers
21+
public init(controller: ASAuthorizationController) {
22+
super.init(parentObject: controller, delegateProxy: ASAuthorizationControllerProxy.self)
23+
}
24+
25+
// MARK: - Methods
26+
static func registerKnownImplementations() {
27+
self.register {
28+
ASAuthorizationControllerProxy(controller: $0)
29+
}
30+
}
31+
32+
static func currentDelegate(for object: ASAuthorizationController) -> ASAuthorizationControllerDelegate? {
33+
return object.delegate
34+
}
35+
36+
static func setCurrentDelegate(_ delegate: ASAuthorizationControllerDelegate?, to object: ASAuthorizationController) {
37+
object.delegate = delegate
38+
}
39+
40+
// MARK: - Completed
41+
deinit {
42+
self.didComplete.onCompleted()
43+
}
44+
}
45+
46+
// MARK: - ASAuthorizationControllerDelegate
47+
extension ASAuthorizationControllerProxy: ASAuthorizationControllerDelegate {
48+
49+
func authorizationController(
50+
controller: ASAuthorizationController,
51+
didCompleteWithAuthorization authorization: ASAuthorization
52+
) {
53+
didComplete.onNext(authorization)
54+
didComplete.onCompleted()
55+
}
56+
57+
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
58+
if (error as NSError).code == ASAuthorizationError.canceled.rawValue {
59+
didComplete.onCompleted()
60+
return
61+
}
62+
didComplete.onError(error)
63+
}
64+
}
65+
66+
// MARK: - ASAuthorizationControllerPresentationContextProviding
67+
extension ASAuthorizationControllerProxy: ASAuthorizationControllerPresentationContextProviding {
68+
69+
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
70+
return self.presentationWindow
71+
}
72+
}
73+
74+
extension Reactive where Base: ASAuthorizationAppleIDProvider {
75+
76+
public func login(scope: [ASAuthorization.Scope]? = nil) -> Observable<ASAuthorization> {
77+
let request = base.createRequest()
78+
request.requestedScopes = scope
79+
80+
let controller = ASAuthorizationController(authorizationRequests: [request])
81+
82+
let proxy = ASAuthorizationControllerProxy.proxy(for: controller)
83+
84+
controller.presentationContextProvider = proxy
85+
controller.performRequests()
86+
87+
return proxy.didComplete
88+
}
89+
}

0 commit comments

Comments
 (0)