@@ -122,6 +122,10 @@ public final class AuthService {
122122 // Needed because provider data sign-in doesn't distinguish between email link and password
123123 // sign-in needed for reauthentication
124124 @ObservationIgnored @AppStorage ( " is-email-link " ) private var isEmailLinkSignIn : Bool = false
125+ // Storage for email link reauthentication (separate from sign-in)
126+ @ObservationIgnored @AppStorage ( " email-link-reauth " ) private var emailLinkReauth : String ?
127+ @ObservationIgnored @AppStorage ( " is-reauthenticating " ) private var isReauthenticating : Bool =
128+ false
125129
126130 private var currentMFAResolver : MultiFactorResolver ?
127131 private var listenerManager : AuthListenerManager ?
@@ -181,6 +185,9 @@ public final class AuthService {
181185 currentUser = nil
182186 // Clear email link sign-in flag
183187 isEmailLinkSignIn = false
188+ // Clear email link reauth state
189+ emailLinkReauth = nil
190+ isReauthenticating = false
184191 updateAuthenticationState ( )
185192 }
186193
@@ -385,20 +392,44 @@ public extension AuthService {
385392// MARK: - Email Link Sign In
386393
387394public extension AuthService {
388- func sendEmailSignInLink( email: String ) async throws {
395+ /// Send email link for sign-in or reauthentication
396+ /// - Parameters:
397+ /// - email: Email address to send link to
398+ /// - isReauth: Whether this is for reauthentication (default: false)
399+ func sendEmailSignInLink( email: String , isReauth: Bool = false ) async throws {
389400 let actionCodeSettings = try updateActionCodeSettings ( )
390401 try await auth. sendSignInLink (
391402 toEmail: email,
392403 actionCodeSettings: actionCodeSettings
393404 )
405+
406+ // Store email based on context
407+ if isReauth {
408+ emailLinkReauth = email
409+ isReauthenticating = true
410+ }
394411 }
395412
396413 func handleSignInLink( url url: URL ) async throws {
397414 do {
398- guard let email = emailLink else {
399- throw AuthServiceError
400- . invalidEmailLink ( " email address is missing from app storage. Is this the same device? " )
415+ // Check which flow we're in based on the flag
416+ let email : String
417+ let isReauth = isReauthenticating
418+
419+ if isReauth {
420+ guard let reauthEmail = emailLinkReauth else {
421+ throw AuthServiceError
422+ . invalidEmailLink ( " Email address is missing for reauthentication " )
423+ }
424+ email = reauthEmail
425+ } else {
426+ guard let signInEmail = emailLink else {
427+ throw AuthServiceError
428+ . invalidEmailLink ( " email address is missing from app storage. Is this the same device? " )
429+ }
430+ email = signInEmail
401431 }
432+
402433 let urlString = url. absoluteString
403434
404435 guard let originalLink = CommonUtils . getQueryParamValue ( from: urlString, paramName: " link " )
@@ -412,41 +443,62 @@ public extension AuthService {
412443 . invalidEmailLink ( " Failed to decode Link URL " )
413444 }
414445
415- guard let continueUrl = CommonUtils . getQueryParamValue ( from: link, paramName: " continueUrl " )
416- else {
417- throw AuthServiceError
418- . invalidEmailLink ( " `continueUrl` parameter is missing from the email link URL " )
419- }
420-
421446 if auth. isSignIn ( withEmailLink: link) {
422- let anonymousUserID = CommonUtils . getQueryParamValue (
423- from: continueUrl,
424- paramName: " ui_auid "
425- )
426- if shouldHandleAnonymousUpgrade, anonymousUserID == currentUser? . uid {
427- let credential = EmailAuthProvider . credential ( withEmail: email, link: link)
428- try await handleAutoUpgradeAnonymousUser ( credentials: credential)
447+ let credential = EmailAuthProvider . credential ( withEmail: email, link: link)
448+
449+ if isReauth {
450+ // Reauthentication flow
451+ try await reauthenticate ( with: credential)
452+ // Clean up reauth state
453+ emailLinkReauth = nil
454+ isReauthenticating = false
429455 } else {
430- let result = try await auth. signIn ( withEmail: email, link: link)
456+ // Sign-in flow
457+ guard let continueUrl = CommonUtils . getQueryParamValue (
458+ from: link,
459+ paramName: " continueUrl "
460+ )
461+ else {
462+ throw AuthServiceError
463+ . invalidEmailLink ( " `continueUrl` parameter is missing from the email link URL " )
464+ }
465+
466+ let anonymousUserID = CommonUtils . getQueryParamValue (
467+ from: continueUrl,
468+ paramName: " ui_auid "
469+ )
470+ if shouldHandleAnonymousUpgrade, anonymousUserID == currentUser? . uid {
471+ try await handleAutoUpgradeAnonymousUser ( credentials: credential)
472+ } else {
473+ let result = try await auth. signIn ( withEmail: email, link: link)
474+ }
475+ updateAuthenticationState ( )
476+ // Track that user signed in with email link
477+ isEmailLinkSignIn = true
478+ emailLink = nil
431479 }
432- updateAuthenticationState ( )
433- // Track that user signed in with email link
434- isEmailLinkSignIn = true
435- emailLink = nil
436480 }
437481 } catch {
438- // Reconstruct credential for conflict handling
482+ // Determine which email to use for error handling
483+ let email = isReauthenticating ? emailLinkReauth : emailLink
439484 let link = url. absoluteString
440- guard let email = emailLink else {
485+
486+ guard let email = email else {
441487 throw AuthServiceError
442- . invalidEmailLink ( " email address is missing from app storage. Is this the same device? " )
488+ . invalidEmailLink ( " email address is missing from app storage " )
443489 }
444490 let credential = EmailAuthProvider . credential ( withEmail: email, link: link)
445491
446- // Possible conflicts from auth.signIn(withEmail:link:):
447- // - accountExistsWithDifferentCredential: account exists with different provider
448- // - credentialAlreadyInUse: credential is already linked to another account
449- try handleErrorWithConflictCheck ( error: error, credential: credential)
492+ // Only handle conflicts for sign-in flow, not reauth
493+ if !isReauthenticating {
494+ // Possible conflicts from auth.signIn(withEmail:link:):
495+ // - accountExistsWithDifferentCredential: account exists with different provider
496+ // - credentialAlreadyInUse: credential is already linked to another account
497+ try handleErrorWithConflictCheck ( error: error, credential: credential)
498+ } else {
499+ // For reauth, just rethrow
500+ throw error
501+ }
450502 }
451503 }
452504}
0 commit comments