Skip to content

Commit 920673c

Browse files
Merge pull request #1245 from firebase/update-account
2 parents fb8728b + 842caa6 commit 920673c

File tree

5 files changed

+160
-36
lines changed

5 files changed

+160
-36
lines changed

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService+Email.swift

+24-1
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,36 @@ extension EmailPasswordOperationReauthentication {
2828
}
2929
}
3030

31-
class EmailPasswordDeleteUserOperation: DeleteUserOperation,
31+
class EmailPasswordDeleteUserOperation: AuthenticatedOperation,
3232
EmailPasswordOperationReauthentication {
3333
let passwordPrompt: PasswordPromptCoordinator
3434

3535
init(passwordPrompt: PasswordPromptCoordinator) {
3636
self.passwordPrompt = passwordPrompt
3737
}
38+
39+
func callAsFunction(on user: User) async throws {
40+
try await callAsFunction(on: user) {
41+
try await user.delete()
42+
}
43+
}
44+
}
45+
46+
class EmailPasswordUpdatePasswordOperation: AuthenticatedOperation,
47+
EmailPasswordOperationReauthentication {
48+
let passwordPrompt: PasswordPromptCoordinator
49+
let newPassword: String
50+
51+
init(passwordPrompt: PasswordPromptCoordinator, newPassword: String) {
52+
self.passwordPrompt = passwordPrompt
53+
self.newPassword = newPassword
54+
}
55+
56+
func callAsFunction(on user: User) async throws {
57+
try await callAsFunction(on: user) {
58+
try await user.updatePassword(to: newPassword)
59+
}
60+
}
3861
}
3962

4063
@MainActor

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AccountService.swift

+5-13
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,19 @@ enum AuthenticationToken {
1919
protocol AuthenticatedOperation {
2020
func callAsFunction(on user: User) async throws
2121
func reauthenticate() async throws -> AuthenticationToken
22-
func performOperation(on user: User, with token: AuthenticationToken?) async throws
2322
}
2423

2524
extension AuthenticatedOperation {
26-
func callAsFunction(on user: User) async throws {
25+
func callAsFunction(on _: User,
26+
_ performOperation: () async throws -> Void) async throws {
2727
do {
28-
try await performOperation(on: user, with: nil)
28+
try await performOperation()
2929
} catch let error as NSError where error.requiresReauthentication {
3030
let token = try await reauthenticate()
31-
try await performOperation(on: user, with: token)
31+
try await performOperation()
3232
} catch AuthServiceError.reauthenticationRequired {
3333
let token = try await reauthenticate()
34-
try await performOperation(on: user, with: token)
34+
try await performOperation()
3535
}
3636
}
3737
}
38-
39-
protocol DeleteUserOperation: AuthenticatedOperation {}
40-
41-
extension DeleteUserOperation {
42-
func performOperation(on user: User, with _: AuthenticationToken? = nil) async throws {
43-
try await user.delete()
44-
}
45-
}

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Services/AuthService.swift

+19
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public enum AuthView {
3333
case authPicker
3434
case passwordRecovery
3535
case emailLink
36+
case updatePassword
3637
}
3738

3839
@MainActor
@@ -223,6 +224,24 @@ public extension AuthService {
223224
throw error
224225
}
225226
}
227+
228+
func updatePassword(to password: String) async throws {
229+
do {
230+
if let user = auth.currentUser {
231+
let operation = EmailPasswordUpdatePasswordOperation(
232+
passwordPrompt: passwordPrompt,
233+
newPassword: password
234+
)
235+
try await operation(on: user)
236+
}
237+
238+
} catch {
239+
errorMessage = string.localizedErrorMessage(
240+
for: error
241+
)
242+
throw error
243+
}
244+
}
226245
}
227246

228247
// MARK: - Email/Password Sign In

FirebaseSwiftUI/FirebaseAuthSwiftUI/Sources/Views/SignedInView.swift

+30-22
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,40 @@ extension SignedInView: View {
1414
}
1515

1616
public var body: some View {
17-
VStack {
18-
Text("Signed in")
19-
Text("User: \(authService.currentUser?.email ?? "Unknown")")
17+
if authService.authView == .updatePassword {
18+
UpdatePasswordView()
19+
} else {
20+
VStack {
21+
Text("Signed in")
22+
Text("User: \(authService.currentUser?.email ?? "Unknown")")
2023

21-
if authService.currentUser?.isEmailVerified == false {
22-
VerifyEmailView()
23-
}
24-
25-
Button("Sign out") {
26-
Task {
27-
do {
28-
try await authService.signOut()
29-
} catch {}
24+
if authService.currentUser?.isEmailVerified == false {
25+
VerifyEmailView()
3026
}
31-
}
32-
Divider()
33-
Button("Delete account") {
34-
Task {
35-
do {
36-
try await authService.deleteUser()
37-
} catch {}
27+
Divider()
28+
Button("Update password") {
29+
authService.authView = .updatePassword
30+
}
31+
Divider()
32+
Button("Sign out") {
33+
Task {
34+
do {
35+
try await authService.signOut()
36+
} catch {}
37+
}
38+
}
39+
Divider()
40+
Button("Delete account") {
41+
Task {
42+
do {
43+
try await authService.deleteUser()
44+
} catch {}
45+
}
3846
}
47+
Text(authService.errorMessage).foregroundColor(.red)
48+
}.sheet(isPresented: isShowingPasswordPrompt) {
49+
PasswordPromptSheet(coordinator: authService.passwordPrompt)
3950
}
40-
Text(authService.errorMessage).foregroundColor(.red)
41-
}.sheet(isPresented: isShowingPasswordPrompt) {
42-
PasswordPromptSheet(coordinator: authService.passwordPrompt)
4351
}
4452
}
4553
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//
2+
// UpdatePassword.swift
3+
// FirebaseUI
4+
//
5+
// Created by Russell Wheatley on 24/04/2025.
6+
//
7+
8+
import SwiftUI
9+
10+
private enum FocusableField: Hashable {
11+
case password
12+
case confirmPassword
13+
}
14+
15+
@MainActor
16+
public struct UpdatePasswordView {
17+
@Environment(AuthService.self) private var authService
18+
@State private var password = ""
19+
@State private var confirmPassword = ""
20+
21+
@FocusState private var focus: FocusableField?
22+
private var isValid: Bool {
23+
!password.isEmpty && password == confirmPassword
24+
}
25+
}
26+
27+
extension UpdatePasswordView: View {
28+
private var isShowingPasswordPrompt: Binding<Bool> {
29+
Binding(
30+
get: { authService.passwordPrompt.isPromptingPassword },
31+
set: { authService.passwordPrompt.isPromptingPassword = $0 }
32+
)
33+
}
34+
35+
public var body: some View {
36+
VStack {
37+
LabeledContent {
38+
SecureField("Password", text: $password)
39+
.focused($focus, equals: .password)
40+
.submitLabel(.go)
41+
} label: {
42+
Image(systemName: "lock")
43+
}
44+
.padding(.vertical, 6)
45+
.background(Divider(), alignment: .bottom)
46+
.padding(.bottom, 8)
47+
48+
Divider()
49+
50+
LabeledContent {
51+
SecureField("Confirm password", text: $confirmPassword)
52+
.focused($focus, equals: .confirmPassword)
53+
.submitLabel(.go)
54+
} label: {
55+
Image(systemName: "lock")
56+
}
57+
.padding(.vertical, 6)
58+
.background(Divider(), alignment: .bottom)
59+
.padding(.bottom, 8)
60+
61+
Divider()
62+
63+
Button(action: {
64+
Task {
65+
try await authService.updatePassword(to: confirmPassword)
66+
authService.authView = .authPicker
67+
}
68+
}, label: {
69+
Text("Update password")
70+
.padding(.vertical, 8)
71+
.frame(maxWidth: .infinity)
72+
73+
})
74+
.disabled(!isValid)
75+
.padding([.top, .bottom], 8)
76+
.frame(maxWidth: .infinity)
77+
.buttonStyle(.borderedProminent)
78+
}.sheet(isPresented: isShowingPasswordPrompt) {
79+
PasswordPromptSheet(coordinator: authService.passwordPrompt)
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)