Skip to content

Commit ff8bf8e

Browse files
fix: Implement state recovery for DPoP enabled WebAuth flow after process death
1 parent 65be2b4 commit ff8bf8e

1 file changed

Lines changed: 42 additions & 1 deletion

File tree

auth0/src/main/java/com/auth0/android/provider/WebAuthProvider.kt

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,43 @@ public object WebAuthProvider : SenderConstraining<WebAuthProvider> {
3838
private val callbacks = CopyOnWriteArraySet<Callback<Credentials, AuthenticationException>>()
3939
private val parCallbacks = CopyOnWriteArraySet<Callback<AuthorizationCode, AuthenticationException>>()
4040

41+
// Buffers a state-restore result that completed before any callback was registered (process
42+
// death during login: the restored AuthenticationActivity finishes the token exchange before
43+
// the host app can subscribe). Delivered to the next addCallback subscriber.
44+
private sealed class RecoveredResult {
45+
class Success(val credentials: Credentials) : RecoveredResult()
46+
class Failure(val error: AuthenticationException) : RecoveredResult()
47+
}
48+
49+
private val recoveryLock = Any()
50+
private var pendingRecovered: RecoveredResult? = null
51+
4152
@JvmStatic
4253
@get:VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
4354
internal var managerInstance: ResumableManager? = null
4455
private set
4556

57+
/**
58+
* Registers a callback for Universal Login results from the state-restore path
59+
* ([onRestoreInstanceState]). A result buffered before this call is delivered immediately and
60+
* consumed. Normal in-process logins resolve through the [Builder.start] callback, not here.
61+
*/
4662
@JvmStatic
4763
public fun addCallback(callback: Callback<Credentials, AuthenticationException>) {
48-
callbacks += callback
64+
val buffered = synchronized(recoveryLock) {
65+
val pending = pendingRecovered
66+
if (pending != null) {
67+
pendingRecovered = null
68+
} else {
69+
callbacks += callback
70+
}
71+
pending
72+
}
73+
when (buffered) {
74+
is RecoveredResult.Success -> callback.onSuccess(buffered.credentials)
75+
is RecoveredResult.Failure -> callback.onFailure(buffered.error)
76+
null -> {}
77+
}
4978
}
5079

5180
@JvmStatic
@@ -152,12 +181,24 @@ public object WebAuthProvider : SenderConstraining<WebAuthProvider> {
152181
state,
153182
object : Callback<Credentials, AuthenticationException> {
154183
override fun onSuccess(result: Credentials) {
184+
synchronized(recoveryLock) {
185+
if (callbacks.isEmpty()) {
186+
pendingRecovered = RecoveredResult.Success(result)
187+
return
188+
}
189+
}
155190
for (callback in callbacks) {
156191
callback.onSuccess(result)
157192
}
158193
}
159194

160195
override fun onFailure(error: AuthenticationException) {
196+
synchronized(recoveryLock) {
197+
if (callbacks.isEmpty()) {
198+
pendingRecovered = RecoveredResult.Failure(error)
199+
return
200+
}
201+
}
161202
for (callback in callbacks) {
162203
callback.onFailure(error)
163204
}

0 commit comments

Comments
 (0)