Chapter uses passwordless authentication via Supabase Auth. Users sign in with email magic links and verify their accounts with one-time passwords (OTP).
The sign-up process creates a Supabase Auth user and inserts a record into the users_v2 table:
AuthManager.swift:291-403
func signUp() async throws -> Bool { guard !isLoading else { return false } DispatchQueue.main.async { self.isLoading = true } defer { DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak self] in self?.isLoading = false } } do { let authResponse = try await supabase.auth.signUp( email: email, password: generateSecurePassword() ) if checkIfEmailExists(res: authResponse) { DispatchQueue.main.async { [weak self] in self?.errorMessage = "This email is already registered. Please use a different email or sign in." self?.showErrorAlert = true ErrorService.shared.showError( title: "Email Already Registered", description: "This email is already registered. Please use a different email or sign in." ) } return false } // Store the user ID for future use let userId = authResponse.user.id var dateComponents = DateComponents() dateComponents.day = Int(selectedDay) dateComponents.month = Int(selectedMonth) dateComponents.year = Int(selectedYear) dateComponents.hour = 12 dateComponents.timeZone = TimeZone.current let dobDate = Calendar.current.date(from: dateComponents) let initialUser = GroupUser( id: userId, createdAt: Date(), firstName: firstName, lastName: lastName, email: email, dob: dobDate, isVerified: false, uni_id: university != nil ? university!.id : nil, courseID: course != nil ? course!.id : nil, accomID: accom != nil ? accom!.id : nil, isPrivate: false, onboardingStage: 8, enrollmentStatus: JourneyStage2.toEnrollmentStatus(stage: selectedStageID), uniPlan: nil ) await MainActor.run { currentUser = initialUser } try await supabase.from("users_v2").insert(initialUser).execute() try await uploadFirstStageData(id: userId) try await hubButtonLogic() Mixpanel.mainInstance().identify(distinctId: userId.uuidString) Mixpanel.mainInstance().people.set(properties: ["$email": email]) Mixpanel.mainInstance().track(event: "Onboarding Initial Sign Up") DispatchQueue.main.async { [weak self] in self?.idBackup = userId.uuidString self?.currentUser = initialUser self?.isPendingVerification = true self?.emailAddressBackup = self?.email ?? "" // Store backup data for verification state } return true } catch { print(error) DispatchQueue.main.async { [weak self] in self?.errorMessage = "Error signing up: \(error.localizedDescription)." self?.showErrorAlert = true ErrorService.shared.showError(title: "Error signing up", description: error.localizedDescription) } return false }}
The generateSecurePassword() function creates a random password that the user never sees. This enables passwordless authentication while still having a secure Supabase Auth account.
func checkIfEmailExists(res: AuthResponse) -> Bool { // Check if identities exist and aren't empty // If there are no identities, the email might already be in use return res.user.identities == nil || res.user.identities!.isEmpty}