Skip to content

Commit d1f561c

Browse files
feat: initial implementation of apple architecture
1 parent e838597 commit d1f561c

File tree

4 files changed

+206
-0
lines changed

4 files changed

+206
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// AccountService+Apple.swift
3+
// FirebaseUI
4+
//
5+
// Created by Russell Wheatley on 21/10/2025.
6+
//
7+
8+
@preconcurrency import FirebaseAuth
9+
import FirebaseAuthSwiftUI
10+
import Observation
11+
12+
protocol AppleOperationReauthentication {
13+
var appleProvider: AppleProviderSwift { get }
14+
}
15+
16+
extension AppleOperationReauthentication {
17+
@MainActor func reauthenticate() async throws -> AuthenticationToken {
18+
guard let user = Auth.auth().currentUser else {
19+
throw AuthServiceError.reauthenticationRequired("No user currently signed-in")
20+
}
21+
22+
do {
23+
// TODO: Implement Apple reauthentication
24+
let credential = try await appleProvider.createAuthCredential()
25+
try await user.reauthenticate(with: credential)
26+
27+
return .firebase("")
28+
} catch {
29+
throw AuthServiceError.signInFailed(underlying: error)
30+
}
31+
}
32+
}
33+
34+
@MainActor
35+
class AppleDeleteUserOperation: AuthenticatedOperation,
36+
@preconcurrency AppleOperationReauthentication {
37+
let appleProvider: AppleProviderSwift
38+
init(appleProvider: AppleProviderSwift) {
39+
self.appleProvider = appleProvider
40+
}
41+
42+
func callAsFunction(on user: User) async throws {
43+
// TODO: Implement delete user operation
44+
try await callAsFunction(on: user) {
45+
try await user.delete()
46+
}
47+
}
48+
}
49+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import FirebaseAuth
16+
import FirebaseAuthSwiftUI
17+
import FirebaseCore
18+
import SwiftUI
19+
20+
public class AppleProviderSwift: AuthProviderSwift, DeleteUserSwift {
21+
public let scopes: [String]
22+
let providerId = "apple.com"
23+
24+
public init(scopes: [String] = []) {
25+
self.scopes = scopes
26+
}
27+
28+
@MainActor public func createAuthCredential() async throws -> AuthCredential {
29+
// TODO: Implement Apple Sign In credential creation
30+
// This will need to use ASAuthorizationAppleIDProvider
31+
let provider = OAuthProvider(providerID: providerId)
32+
return try await withCheckedThrowingContinuation { continuation in
33+
provider.getCredentialWith(nil) { credential, error in
34+
if let error {
35+
continuation
36+
.resume(throwing: AuthServiceError.signInFailed(underlying: error))
37+
} else if let credential {
38+
continuation.resume(returning: credential)
39+
} else {
40+
continuation
41+
.resume(throwing: AuthServiceError
42+
.invalidCredentials("Apple did not provide a valid AuthCredential"))
43+
}
44+
}
45+
}
46+
}
47+
48+
public func deleteUser(user: User) async throws {
49+
// TODO: Implement delete user functionality
50+
let operation = AppleDeleteUserOperation(appleProvider: self)
51+
try await operation(on: user)
52+
}
53+
}
54+
55+
public class AppleProviderAuthUI: AuthProviderUI {
56+
public var provider: AuthProviderSwift
57+
58+
public init(provider: AuthProviderSwift) {
59+
self.provider = provider
60+
}
61+
62+
public let id: String = "apple.com"
63+
64+
@MainActor public func authButton() -> AnyView {
65+
AnyView(SignInWithAppleButton(provider: provider))
66+
}
67+
}
68+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//
16+
// AuthService+Apple.swift
17+
// FirebaseUI
18+
//
19+
// Created by Russell Wheatley on 21/10/2025.
20+
//
21+
22+
import FirebaseAuthSwiftUI
23+
24+
public extension AuthService {
25+
@discardableResult
26+
func withAppleSignIn(_ provider: AppleProviderSwift? = nil) -> AuthService {
27+
// TODO: Register Apple provider with authentication service
28+
registerProvider(providerWithButton: AppleProviderAuthUI(provider: provider ??
29+
AppleProviderSwift()))
30+
return self
31+
}
32+
}
33+
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import FirebaseAuthSwiftUI
16+
import SwiftUI
17+
18+
/// A button for signing in with Apple
19+
@MainActor
20+
public struct SignInWithAppleButton {
21+
@Environment(AuthService.self) private var authService
22+
let provider: AuthProviderSwift
23+
public init(provider: AuthProviderSwift) {
24+
self.provider = provider
25+
}
26+
}
27+
28+
extension SignInWithAppleButton: View {
29+
public var body: some View {
30+
Button(action: {
31+
// TODO: Implement sign in with Apple action
32+
Task {
33+
try await authService.signIn(provider)
34+
}
35+
}) {
36+
HStack {
37+
// TODO: Add Apple logo image
38+
Image(systemName: "apple.logo")
39+
.resizable()
40+
.renderingMode(.template)
41+
.scaledToFit()
42+
.frame(width: 24, height: 24)
43+
.foregroundColor(.white)
44+
Text("Sign in with Apple")
45+
.fontWeight(.semibold)
46+
.foregroundColor(.white)
47+
}
48+
.frame(maxWidth: .infinity, alignment: .leading)
49+
.padding()
50+
.background(Color.black)
51+
.cornerRadius(8)
52+
}
53+
.accessibilityIdentifier("sign-in-with-apple-button")
54+
}
55+
}
56+

0 commit comments

Comments
 (0)