Add password reset feature for iOS and Android with deep link support
Implemented complete password reset flow with email verification and deep linking: iOS (SwiftUI): - PasswordResetViewModel: Manages 3-step flow (request, verify, reset) with deep link support - ForgotPasswordView: Email entry screen with success/error states - VerifyResetCodeView: 6-digit code verification with resend option - ResetPasswordView: Password reset with live validation and strength indicators - PasswordResetFlow: Container managing navigation between screens - Deep link handling in iOSApp.swift for mycrib://reset-password?token=xxx - Info.plist: Added CFBundleURLTypes for deep link scheme Android (Compose): - PasswordResetViewModel: StateFlow-based state management with coroutines - ForgotPasswordScreen: Material3 email entry with auto-navigation - VerifyResetCodeScreen: Code verification with Material3 design - ResetPasswordScreen: Password reset with live validation checklist - Deep link handling in MainActivity.kt and AndroidManifest.xml - Token cleanup callback to prevent reusing expired deep link tokens - Shared ViewModel scoping across all password reset screens - Improved error handling for Django validation errors Shared: - Routes: Added ForgotPasswordRoute, VerifyResetCodeRoute, ResetPasswordRoute - AuthApi: Enhanced resetPassword with Django validation error parsing - User models: Added password reset request/response models Security features: - Deep link tokens expire after use - Proper token validation on backend - Session invalidation after password change - Password strength requirements enforced 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,18 @@
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<!-- Deep link for password reset -->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:scheme="mycrib"
|
||||
android:host="reset-password" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package com.example.mycrib
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.mycrib.storage.TokenManager
|
||||
import com.mycrib.storage.TokenStorage
|
||||
@@ -12,6 +17,8 @@ import com.mycrib.storage.TaskCacheManager
|
||||
import com.mycrib.storage.TaskCacheStorage
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
private var deepLinkResetToken by mutableStateOf<String?>(null)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -22,8 +29,33 @@ class MainActivity : ComponentActivity() {
|
||||
// Initialize TaskCacheStorage for offline task caching
|
||||
TaskCacheStorage.initialize(TaskCacheManager.getInstance(applicationContext))
|
||||
|
||||
// Handle deep link from intent
|
||||
handleDeepLink(intent)
|
||||
|
||||
setContent {
|
||||
App()
|
||||
App(
|
||||
deepLinkResetToken = deepLinkResetToken,
|
||||
onClearDeepLinkToken = {
|
||||
deepLinkResetToken = null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
handleDeepLink(intent)
|
||||
}
|
||||
|
||||
private fun handleDeepLink(intent: Intent?) {
|
||||
val data: Uri? = intent?.data
|
||||
if (data != null && data.scheme == "mycrib" && data.host == "reset-password") {
|
||||
// Extract token from query parameter
|
||||
val token = data.getQueryParameter("token")
|
||||
if (token != null) {
|
||||
deepLinkResetToken = token
|
||||
println("Deep link received with token: $token")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,5 +63,5 @@ class MainActivity : ComponentActivity() {
|
||||
@Preview
|
||||
@Composable
|
||||
fun AppAndroidPreview() {
|
||||
App()
|
||||
App(deepLinkResetToken = null)
|
||||
}
|
||||
Reference in New Issue
Block a user