Skip to content

Commit

Permalink
chore(docs): add password recovery example (#548)
Browse files Browse the repository at this point in the history
  • Loading branch information
grdsdev authored Oct 1, 2024
1 parent 36d0fc0 commit 8f61141
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 12 deletions.
4 changes: 4 additions & 0 deletions Examples/Examples.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

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

/* Begin PBXFileReference section */
7928145C2CAB2CDE000B4ADB /* ResetPasswordView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResetPasswordView.swift; sourceTree = "<group>"; };
793895C62954ABFF0044F2B8 /* Examples.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Examples.app; sourceTree = BUILT_PRODUCTS_DIR; };
793895C92954ABFF0044F2B8 /* ExamplesApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplesApp.swift; sourceTree = "<group>"; };
793895CB2954ABFF0044F2B8 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -276,6 +278,7 @@
79B1C80A2BABFF6F00D991AA /* Profile */ = {
isa = PBXGroup;
children = (
7928145C2CAB2CDE000B4ADB /* ResetPasswordView.swift */,
79B1C80B2BABFF8000D991AA /* ProfileView.swift */,
794C61D52BAD1E12000E6B0F /* UserIdentityList.swift */,
79B3261B2BF359A50023661C /* UpdateProfileView.swift */,
Expand Down Expand Up @@ -510,6 +513,7 @@
797EFB662BABD82A00098D6B /* BucketList.swift in Sources */,
79E2B55C2B97A2310042CD21 /* UIApplicationExtensions.swift in Sources */,
794EF1222955F26A008C9526 /* AddTodoListView.swift in Sources */,
7928145D2CAB2CE2000B4ADB /* ResetPasswordView.swift in Sources */,
7956405E2954ADE00088A06F /* Secrets.swift in Sources */,
795640682955AEB30088A06F /* Models.swift in Sources */,
79B1C80C2BABFF8000D991AA /* ProfileView.swift in Sources */,
Expand Down
9 changes: 7 additions & 2 deletions Examples/Examples/Auth/AuthController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SwiftUI
@MainActor
final class AuthController {
var session: Session?
var isPasswordRecoveryFlow: Bool = false

var currentUserID: UUID {
guard let id = session?.user.id else {
Expand All @@ -27,9 +28,13 @@ final class AuthController {
init() {
observeAuthStateChangesTask = Task {
for await (event, session) in supabase.auth.authStateChanges {
guard [.initialSession, .signedIn, .signedOut].contains(event) else { return }
if [.initialSession, .signedIn, .signedOut].contains(event) {
self.session = session
}

self.session = session
if event == .passwordRecovery {
self.isPasswordRecoveryFlow = true
}
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions Examples/Examples/Auth/AuthWithEmailAndPassword.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ struct AuthWithEmailAndPassword: View {
@State var mode: Mode = .signIn
@State var actionState = ActionState<Result, Error>.idle

@State var isPresentingResetPassword: Bool = false

var body: some View {
Form {
Section {
Expand Down Expand Up @@ -73,13 +75,24 @@ struct AuthWithEmailAndPassword: View {
actionState = .idle
}
}

if mode == .signIn {
Section {
Button("Forgot password? Reset it.") {
isPresentingResetPassword = true
}
}
}
}
.onOpenURL { url in
Task {
await onOpenURL(url)
}
}
.animation(.default, value: mode)
.sheet(isPresented: $isPresentingResetPassword) {
ResetPasswordView()
}
}

@MainActor
Expand Down
2 changes: 1 addition & 1 deletion Examples/Examples/Contants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

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

extension URL {
Expand Down
31 changes: 26 additions & 5 deletions Examples/Examples/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ struct HomeView: View {
@State private var mfaStatus: MFAStatus?

var body: some View {
@Bindable var auth = auth

TabView {
ProfileView()
.tabItem {
Expand All @@ -28,11 +30,8 @@ struct HomeView: View {
Label("Storage", systemImage: "externaldrive")
}
}
.task {
// mfaStatus = await verifyMFAStatus()
}
.sheet(unwrapping: $mfaStatus) { $mfaStatus in
MFAFlow(status: mfaStatus)
.sheet(isPresented: $auth.isPasswordRecoveryFlow) {
UpdatePasswordView()
}
}

Expand All @@ -55,6 +54,28 @@ struct HomeView: View {
return nil
}
}

struct UpdatePasswordView: View {
@Environment(\.dismiss) var dismiss

@State var password: String = ""

var body: some View {
Form {
SecureField("Password", text: $password)
.textContentType(.newPassword)

Button("Update password") {
Task {
do {
try await supabase.auth.update(user: UserAttributes(password: password))
dismiss()
} catch {}
}
}
}
}
}
}

struct HomeView_Previews: PreviewProvider {
Expand Down
52 changes: 52 additions & 0 deletions Examples/Examples/Profile/ResetPasswordView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// ResetPasswordView.swift
// Examples
//
// Created by Guilherme Souza on 30/09/24.
//

import SwiftUI
import SwiftUINavigation

struct ResetPasswordView: View {
@State private var email: String = ""
@State private var showAlert = false
@State private var alertMessage = ""

var body: some View {
VStack(spacing: 20) {
Text("Reset Password")
.font(.largeTitle)
.fontWeight(.bold)

TextField("Enter your email", text: $email)
.textFieldStyle(RoundedBorderTextFieldStyle())
.autocapitalization(.none)
.keyboardType(.emailAddress)

Button(action: resetPassword) {
Text("Send Reset Link")
.foregroundColor(.white)
.padding()
.background(Color.blue)
.cornerRadius(10)
}
}
.padding()
.alert("Password reset", isPresented: $showAlert, actions: {}, message: {
Text(alertMessage)
})
}

func resetPassword() {
Task {
do {
try await supabase.auth.resetPasswordForEmail(email)
alertMessage = "Password reset email sent successfully"
} catch {
alertMessage = "Error sending password reset email: \(error.localizedDescription)"
}
showAlert = true
}
}
}
9 changes: 8 additions & 1 deletion Examples/Examples/Profile/UpdateProfileView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ struct UpdateProfileView: View {

@State var email = ""
@State var phone = ""
@State var password = ""

@State var otp = ""
@State var showTokenField = false

var formUpdated: Bool {
emailChanged || phoneChanged
emailChanged || phoneChanged || !password.isEmpty
}

var emailChanged: Bool {
Expand All @@ -42,6 +43,8 @@ struct UpdateProfileView: View {
.keyboardType(.phonePad)
.autocorrectionDisabled()
.textInputAutocapitalization(.never)
SecureField("New password", text: $password)
.textContentType(.newPassword)
}

Section {
Expand Down Expand Up @@ -81,6 +84,10 @@ struct UpdateProfileView: View {
attributes.phone = phone
}

if password.isEmpty == false {
attributes.password = password
}

do {
try await supabase.auth.update(user: attributes, redirectTo: Constants.redirectToURL)

Expand Down
2 changes: 1 addition & 1 deletion Examples/supabase/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ enabled = true
account_sid = "account sid"
message_service_sid = "account service sid"
# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead:
auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)"
auth_token = "auth token"

# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
# `discord`, `facebook`, `github`, `gitlab`, `google`, `twitch`, `twitter`, `slack`, `spotify`.
Expand Down
2 changes: 2 additions & 0 deletions Sources/Auth/AuthClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,8 @@ public final class AuthClient: Sendable {
/// Gets the session data from a OAuth2 callback URL.
@discardableResult
public func session(from url: URL) async throws -> Session {
logger?.debug("received \(url)")

let params = extractParams(from: url)

if configuration.flowType == .implicit, !isImplicitGrantFlow(params: params) {
Expand Down
3 changes: 3 additions & 0 deletions Sources/Auth/Internal/EventEmitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import Helpers

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

func attach(_ listener: @escaping AuthStateChangeListener) -> ObservationToken {
emitter.attach { event in
guard let event else { return }
listener(event.0, event.1)

logger?.verbose("Auth state changed: \(event)")
}
}

Expand Down
5 changes: 3 additions & 2 deletions Sources/Helpers/HTTP/LoggerInterceptor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ package struct LoggerInterceptor: HTTPClientInterceptor {
) async throws -> HTTPResponse {
let id = UUID().uuidString
return try await SupabaseLoggerTaskLocal.$additionalContext.withValue(merging: ["requestID": .string(id)]) {
let urlRequest = request.urlRequest

logger.verbose(
"""
Request: \(request.method.rawValue) \(request.url.absoluteString
.removingPercentEncoding ?? "")
Request: \(urlRequest.httpMethod ?? "") \(urlRequest.url?.absoluteString.removingPercentEncoding ?? "")
Body: \(stringfy(request.body))
"""
)
Expand Down

0 comments on commit 8f61141

Please sign in to comment.