Skip to content

Commit a02800f

Browse files
feat: add DPoP credential state error handling and update dependencies
1 parent e12066b commit a02800f

7 files changed

Lines changed: 81 additions & 5 deletions

File tree

A0Auth0.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
1616
s.source_files = 'ios/**/*.{h,m,mm,swift}'
1717
s.requires_arc = true
1818

19-
s.dependency 'Auth0', '2.18.0'
19+
s.dependency 'Auth0', '2.19.0'
2020

2121
install_modules_dependencies(s)
2222
end

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ dependencies {
9696
implementation "com.facebook.react:react-android"
9797
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
9898
implementation "androidx.browser:browser:1.2.0"
99-
implementation 'com.auth0.android:auth0:3.14.0'
99+
implementation 'com.auth0.android:auth0:3.15.0'
100100
}
101101

102102
if (isNewArchitectureEnabled()) {

android/src/main/java/com/auth0/react/A0Auth0Module.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,10 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0
8282
CredentialsManagerException.BIOMETRICS_INVALID_USER to "BIOMETRICS_INVALID_USER",
8383
CredentialsManagerException.BIOMETRIC_AUTHENTICATION_FAILED to "BIOMETRIC_AUTHENTICATION_FAILED",
8484
CredentialsManagerException.API_ERROR to "API_ERROR",
85-
CredentialsManagerException.NO_NETWORK to "NO_NETWORK"
85+
CredentialsManagerException.NO_NETWORK to "NO_NETWORK",
86+
CredentialsManagerException.DPOP_KEY_MISSING to "DPOP_KEY_MISSING",
87+
CredentialsManagerException.DPOP_NOT_CONFIGURED to "DPOP_NOT_CONFIGURED",
88+
CredentialsManagerException.DPOP_KEY_MISMATCH to "DPOP_KEY_MISMATCH"
8689
)
8790
// DPoP enabled by default
8891
private var useDPoP: Boolean = true

ios/NativeBridge.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,9 @@ extension CredentialsManagerError {
518518
code = "REVOKE_FAILED"
519519
}
520520
case CredentialsManagerError.largeMinTTL: code = "LARGE_MIN_TTL"
521+
case CredentialsManagerError.dpopKeyMissing: code = "DPOP_KEY_MISSING"
522+
case CredentialsManagerError.dpopKeyMismatch: code = "DPOP_KEY_MISMATCH"
523+
case CredentialsManagerError.dpopNotConfigured: code = "DPOP_NOT_CONFIGURED"
521524
default: code = "UNKNOWN"
522525
}
523526
return code

src/core/models/CredentialsManagerError.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ export const CredentialsManagerErrorCodes = {
6161
INCOMPATIBLE_DEVICE: 'INCOMPATIBLE_DEVICE',
6262
/** Cryptographic operation failed */
6363
CRYPTO_EXCEPTION: 'CRYPTO_EXCEPTION',
64+
/** DPoP key pair is no longer available in the device keystore/keychain - re-authentication required */
65+
DPOP_KEY_MISSING: 'DPOP_KEY_MISSING',
66+
/** Credentials are DPoP-bound but the client was not configured with DPoP */
67+
DPOP_NOT_CONFIGURED: 'DPOP_NOT_CONFIGURED',
68+
/** Current DPoP key pair does not match the one used when credentials were saved - re-authentication required */
69+
DPOP_KEY_MISMATCH: 'DPOP_KEY_MISMATCH',
6470
/** Unknown or uncategorized error */
6571
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
6672
} as const;
@@ -80,6 +86,11 @@ const ERROR_CODE_MAP: Record<string, string> = {
8086
NO_NETWORK: CredentialsManagerErrorCodes.NO_NETWORK,
8187
API_ERROR: CredentialsManagerErrorCodes.API_ERROR,
8288

89+
// --- DPoP credential state errors ---
90+
DPOP_KEY_MISSING: CredentialsManagerErrorCodes.DPOP_KEY_MISSING,
91+
DPOP_NOT_CONFIGURED: CredentialsManagerErrorCodes.DPOP_NOT_CONFIGURED,
92+
DPOP_KEY_MISMATCH: CredentialsManagerErrorCodes.DPOP_KEY_MISMATCH,
93+
8394
// --- API Credentials (MRRT) specific codes ---
8495
API_EXCHANGE_FAILED: CredentialsManagerErrorCodes.API_EXCHANGE_FAILED,
8596
// --- Web (@auth0/auth0-spa-js) mappings ---
@@ -175,6 +186,11 @@ const ERROR_CODE_MAP: Record<string, string> = {
175186
* - `NO_NETWORK`: Network connectivity issue
176187
* - `API_ERROR`: Generic API error
177188
*
189+
* ### DPoP Credential State:
190+
* - `DPOP_KEY_MISSING`: DPoP key pair no longer available in keystore/keychain
191+
* - `DPOP_NOT_CONFIGURED`: Credentials are DPoP-bound but client not configured with DPoP
192+
* - `DPOP_KEY_MISMATCH`: Current DPoP key pair doesn't match the one used when credentials were saved
193+
*
178194
* ### Biometric Authentication:
179195
* - `BIOMETRICS_FAILED`: Biometric authentication failed
180196
* - `INCOMPATIBLE_DEVICE`: Device incompatible with secure storage
@@ -285,6 +301,9 @@ export class CredentialsManagerError extends AuthError {
285301
* - `NO_REFRESH_TOKEN`: Refresh token is not available
286302
* - `RENEW_FAILED`: Token renewal failed
287303
* - `API_EXCHANGE_FAILED`: API credentials exchange failed (MRRT)
304+
* - `DPOP_KEY_MISSING`: DPoP key pair no longer in keystore/keychain
305+
* - `DPOP_NOT_CONFIGURED`: Credentials DPoP-bound but client not configured
306+
* - `DPOP_KEY_MISMATCH`: DPoP key pair doesn't match saved credentials
288307
* - `STORE_FAILED`: Failed to store credentials
289308
* - `REVOKE_FAILED`: Failed to revoke refresh token
290309
* - `LARGE_MIN_TTL`: Requested minimum TTL exceeds token lifetime

src/core/models/__tests__/ErrorCodes.spec.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,20 @@ describe('Error Code Constants', () => {
108108
'CRYPTO_EXCEPTION'
109109
);
110110
expect(CredentialsManagerErrorCodes.UNKNOWN_ERROR).toBe('UNKNOWN_ERROR');
111+
expect(CredentialsManagerErrorCodes.DPOP_KEY_MISSING).toBe(
112+
'DPOP_KEY_MISSING'
113+
);
114+
expect(CredentialsManagerErrorCodes.DPOP_NOT_CONFIGURED).toBe(
115+
'DPOP_NOT_CONFIGURED'
116+
);
117+
expect(CredentialsManagerErrorCodes.DPOP_KEY_MISMATCH).toBe(
118+
'DPOP_KEY_MISMATCH'
119+
);
111120
});
112121

113-
it('should have exactly 15 error codes', () => {
122+
it('should have exactly 18 error codes', () => {
114123
const keys = Object.keys(CredentialsManagerErrorCodes);
115-
expect(keys).toHaveLength(15);
124+
expect(keys).toHaveLength(18);
116125
});
117126

118127
it('should be immutable (as const)', () => {

src/platforms/native/adapters/__tests__/NativeCredentialsManager.errors.spec.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,48 @@ describe('Common Error Handling - NativeCredentialsManager', () => {
162162
});
163163
});
164164

165+
describe('DPoP Credential State Errors', () => {
166+
const dpopErrorTestCases = [
167+
{
168+
code: 'DPOP_KEY_MISSING',
169+
message:
170+
'The stored credentials are DPoP-bound but the DPoP key pair is no longer available in the keystore.',
171+
expectedType: 'DPOP_KEY_MISSING',
172+
},
173+
{
174+
code: 'DPOP_NOT_CONFIGURED',
175+
message:
176+
'The stored credentials are DPoP-bound but the client was not configured with DPoP.',
177+
expectedType: 'DPOP_NOT_CONFIGURED',
178+
},
179+
{
180+
code: 'DPOP_KEY_MISMATCH',
181+
message:
182+
'The stored credentials are DPoP-bound but the current DPoP key pair does not match the one used when credentials were saved.',
183+
expectedType: 'DPOP_KEY_MISMATCH',
184+
},
185+
];
186+
187+
dpopErrorTestCases.forEach(({ code, message, expectedType }) => {
188+
it(`should handle ${code} error`, async () => {
189+
const nativeError = { code, message };
190+
mockBridge.getCredentials.mockRejectedValue(nativeError);
191+
192+
await expect(manager.getCredentials()).rejects.toThrow(
193+
CredentialsManagerError
194+
);
195+
196+
try {
197+
await manager.getCredentials();
198+
} catch (e) {
199+
const err = e as CredentialsManagerError;
200+
expect(err.type).toBe(expectedType);
201+
expect(err.message).toBe(message);
202+
}
203+
});
204+
});
205+
});
206+
165207
describe('Android Biometric Error Mappings', () => {
166208
const biometricErrorTestCases = [
167209
'BIOMETRIC_NO_ACTIVITY',

0 commit comments

Comments
 (0)