Skip to content

Commit 8f61141

Browse files
authored
chore(docs): add password recovery example (#548)
1 parent 36d0fc0 commit 8f61141

File tree

11 files changed

+120
-12
lines changed

11 files changed

+120
-12
lines changed

Examples/Examples.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
7928145D2CAB2CE2000B4ADB /* ResetPasswordView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7928145C2CAB2CDE000B4ADB /* ResetPasswordView.swift */; };
1011
793895CA2954ABFF0044F2B8 /* ExamplesApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 793895C92954ABFF0044F2B8 /* ExamplesApp.swift */; };
1112
793895CC2954ABFF0044F2B8 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 793895CB2954ABFF0044F2B8 /* RootView.swift */; };
1213
793895CE2954AC000044F2B8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 793895CD2954AC000044F2B8 /* Assets.xcassets */; };
@@ -77,6 +78,7 @@
7778
/* End PBXBuildFile section */
7879

7980
/* Begin PBXFileReference section */
81+
7928145C2CAB2CDE000B4ADB /* ResetPasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPasswordView.swift; sourceTree = "<group>"; };
8082
793895C62954ABFF0044F2B8 /* Examples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Examples.app; sourceTree = BUILT_PRODUCTS_DIR; };
8183
793895C92954ABFF0044F2B8 /* ExamplesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplesApp.swift; sourceTree = "<group>"; };
8284
793895CB2954ABFF0044F2B8 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
@@ -276,6 +278,7 @@
276278
79B1C80A2BABFF6F00D991AA /* Profile */ = {
277279
isa = PBXGroup;
278280
children = (
281+
7928145C2CAB2CDE000B4ADB /* ResetPasswordView.swift */,
279282
79B1C80B2BABFF8000D991AA /* ProfileView.swift */,
280283
794C61D52BAD1E12000E6B0F /* UserIdentityList.swift */,
281284
79B3261B2BF359A50023661C /* UpdateProfileView.swift */,
@@ -510,6 +513,7 @@
510513
797EFB662BABD82A00098D6B /* BucketList.swift in Sources */,
511514
79E2B55C2B97A2310042CD21 /* UIApplicationExtensions.swift in Sources */,
512515
794EF1222955F26A008C9526 /* AddTodoListView.swift in Sources */,
516+
7928145D2CAB2CE2000B4ADB /* ResetPasswordView.swift in Sources */,
513517
7956405E2954ADE00088A06F /* Secrets.swift in Sources */,
514518
795640682955AEB30088A06F /* Models.swift in Sources */,
515519
79B1C80C2BABFF8000D991AA /* ProfileView.swift in Sources */,

Examples/Examples/Auth/AuthController.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import SwiftUI
1212
@MainActor
1313
final class AuthController {
1414
var session: Session?
15+
var isPasswordRecoveryFlow: Bool = false
1516

1617
var currentUserID: UUID {
1718
guard let id = session?.user.id else {
@@ -27,9 +28,13 @@ final class AuthController {
2728
init() {
2829
observeAuthStateChangesTask = Task {
2930
for await (event, session) in supabase.auth.authStateChanges {
30-
guard [.initialSession, .signedIn, .signedOut].contains(event) else { return }
31+
if [.initialSession, .signedIn, .signedOut].contains(event) {
32+
self.session = session
33+
}
3134

32-
self.session = session
35+
if event == .passwordRecovery {
36+
self.isPasswordRecoveryFlow = true
37+
}
3338
}
3439
}
3540
}

Examples/Examples/Auth/AuthWithEmailAndPassword.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ struct AuthWithEmailAndPassword: View {
2323
@State var mode: Mode = .signIn
2424
@State var actionState = ActionState<Result, Error>.idle
2525

26+
@State var isPresentingResetPassword: Bool = false
27+
2628
var body: some View {
2729
Form {
2830
Section {
@@ -73,13 +75,24 @@ struct AuthWithEmailAndPassword: View {
7375
actionState = .idle
7476
}
7577
}
78+
79+
if mode == .signIn {
80+
Section {
81+
Button("Forgot password? Reset it.") {
82+
isPresentingResetPassword = true
83+
}
84+
}
85+
}
7686
}
7787
.onOpenURL { url in
7888
Task {
7989
await onOpenURL(url)
8090
}
8191
}
8292
.animation(.default, value: mode)
93+
.sheet(isPresented: $isPresentingResetPassword) {
94+
ResetPasswordView()
95+
}
8396
}
8497

8598
@MainActor

Examples/Examples/Contants.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import Foundation
99

1010
enum Constants {
11-
static let redirectToURL = URL(scheme: "com.supabase.swift-examples")!
11+
static let redirectToURL = URL(string: "com.supabase.swift-examples://")!
1212
}
1313

1414
extension URL {

Examples/Examples/HomeView.swift

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ struct HomeView: View {
1414
@State private var mfaStatus: MFAStatus?
1515

1616
var body: some View {
17+
@Bindable var auth = auth
18+
1719
TabView {
1820
ProfileView()
1921
.tabItem {
@@ -28,11 +30,8 @@ struct HomeView: View {
2830
Label("Storage", systemImage: "externaldrive")
2931
}
3032
}
31-
.task {
32-
// mfaStatus = await verifyMFAStatus()
33-
}
34-
.sheet(unwrapping: $mfaStatus) { $mfaStatus in
35-
MFAFlow(status: mfaStatus)
33+
.sheet(isPresented: $auth.isPasswordRecoveryFlow) {
34+
UpdatePasswordView()
3635
}
3736
}
3837

@@ -55,6 +54,28 @@ struct HomeView: View {
5554
return nil
5655
}
5756
}
57+
58+
struct UpdatePasswordView: View {
59+
@Environment(\.dismiss) var dismiss
60+
61+
@State var password: String = ""
62+
63+
var body: some View {
64+
Form {
65+
SecureField("Password", text: $password)
66+
.textContentType(.newPassword)
67+
68+
Button("Update password") {
69+
Task {
70+
do {
71+
try await supabase.auth.update(user: UserAttributes(password: password))
72+
dismiss()
73+
} catch {}
74+
}
75+
}
76+
}
77+
}
78+
}
5879
}
5980

6081
struct HomeView_Previews: PreviewProvider {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//
2+
// ResetPasswordView.swift
3+
// Examples
4+
//
5+
// Created by Guilherme Souza on 30/09/24.
6+
//
7+
8+
import SwiftUI
9+
import SwiftUINavigation
10+
11+
struct ResetPasswordView: View {
12+
@State private var email: String = ""
13+
@State private var showAlert = false
14+
@State private var alertMessage = ""
15+
16+
var body: some View {
17+
VStack(spacing: 20) {
18+
Text("Reset Password")
19+
.font(.largeTitle)
20+
.fontWeight(.bold)
21+
22+
TextField("Enter your email", text: $email)
23+
.textFieldStyle(RoundedBorderTextFieldStyle())
24+
.autocapitalization(.none)
25+
.keyboardType(.emailAddress)
26+
27+
Button(action: resetPassword) {
28+
Text("Send Reset Link")
29+
.foregroundColor(.white)
30+
.padding()
31+
.background(Color.blue)
32+
.cornerRadius(10)
33+
}
34+
}
35+
.padding()
36+
.alert("Password reset", isPresented: $showAlert, actions: {}, message: {
37+
Text(alertMessage)
38+
})
39+
}
40+
41+
func resetPassword() {
42+
Task {
43+
do {
44+
try await supabase.auth.resetPasswordForEmail(email)
45+
alertMessage = "Password reset email sent successfully"
46+
} catch {
47+
alertMessage = "Error sending password reset email: \(error.localizedDescription)"
48+
}
49+
showAlert = true
50+
}
51+
}
52+
}

Examples/Examples/Profile/UpdateProfileView.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ struct UpdateProfileView: View {
1313

1414
@State var email = ""
1515
@State var phone = ""
16+
@State var password = ""
1617

1718
@State var otp = ""
1819
@State var showTokenField = false
1920

2021
var formUpdated: Bool {
21-
emailChanged || phoneChanged
22+
emailChanged || phoneChanged || !password.isEmpty
2223
}
2324

2425
var emailChanged: Bool {
@@ -42,6 +43,8 @@ struct UpdateProfileView: View {
4243
.keyboardType(.phonePad)
4344
.autocorrectionDisabled()
4445
.textInputAutocapitalization(.never)
46+
SecureField("New password", text: $password)
47+
.textContentType(.newPassword)
4548
}
4649

4750
Section {
@@ -81,6 +84,10 @@ struct UpdateProfileView: View {
8184
attributes.phone = phone
8285
}
8386

87+
if password.isEmpty == false {
88+
attributes.password = password
89+
}
90+
8491
do {
8592
try await supabase.auth.update(user: attributes, redirectTo: Constants.redirectToURL)
8693

Examples/supabase/config.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ enabled = true
8585
account_sid = "account sid"
8686
message_service_sid = "account service sid"
8787
# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
88-
auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
88+
auth_token = "auth token"
8989

9090
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
9191
# `discord`, `facebook`, `github`, `gitlab`, `google`, `twitch`, `twitter`, `slack`, `spotify`.

Sources/Auth/AuthClient.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,8 @@ public final class AuthClient: Sendable {
670670
/// Gets the session data from a OAuth2 callback URL.
671671
@discardableResult
672672
public func session(from url: URL) async throws -> Session {
673+
logger?.debug("received \(url)")
674+
673675
let params = extractParams(from: url)
674676

675677
if configuration.flowType == .implicit, !isImplicitGrantFlow(params: params) {

Sources/Auth/Internal/EventEmitter.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import Helpers
44

55
struct AuthStateChangeEventEmitter {
66
var emitter = EventEmitter<(AuthChangeEvent, Session?)?>(initialEvent: nil, emitsLastEventWhenAttaching: false)
7+
var logger: (any SupabaseLogger)?
78

89
func attach(_ listener: @escaping AuthStateChangeListener) -> ObservationToken {
910
emitter.attach { event in
1011
guard let event else { return }
1112
listener(event.0, event.1)
13+
14+
logger?.verbose("Auth state changed: \(event)")
1215
}
1316
}
1417

0 commit comments

Comments
 (0)