Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion A0Auth0.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Pod::Spec.new do |s|
s.source_files = 'ios/**/*.{h,m,mm,swift}'
s.requires_arc = true

s.dependency 'Auth0', '2.18.0'
s.dependency 'Auth0', '2.19.0'

install_modules_dependencies(s)
end
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ dependencies {
implementation "com.facebook.react:react-android"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.browser:browser:1.2.0"
implementation 'com.auth0.android:auth0:3.14.0'
implementation 'com.auth0.android:auth0:3.15.0'
}

if (isNewArchitectureEnabled()) {
Expand Down
5 changes: 4 additions & 1 deletion android/src/main/java/com/auth0/react/A0Auth0Module.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@ class A0Auth0Module(private val reactContext: ReactApplicationContext) : A0Auth0
CredentialsManagerException.BIOMETRICS_INVALID_USER to "BIOMETRICS_INVALID_USER",
CredentialsManagerException.BIOMETRIC_AUTHENTICATION_FAILED to "BIOMETRIC_AUTHENTICATION_FAILED",
CredentialsManagerException.API_ERROR to "API_ERROR",
CredentialsManagerException.NO_NETWORK to "NO_NETWORK"
CredentialsManagerException.NO_NETWORK to "NO_NETWORK",
CredentialsManagerException.DPOP_KEY_MISSING to "DPOP_KEY_MISSING",
CredentialsManagerException.DPOP_NOT_CONFIGURED to "DPOP_NOT_CONFIGURED",
CredentialsManagerException.DPOP_KEY_MISMATCH to "DPOP_KEY_MISMATCH"
)
// DPoP enabled by default
private var useDPoP: Boolean = true
Expand Down
3 changes: 3 additions & 0 deletions ios/NativeBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,9 @@ extension CredentialsManagerError {
code = "REVOKE_FAILED"
}
case CredentialsManagerError.largeMinTTL: code = "LARGE_MIN_TTL"
case CredentialsManagerError.dpopKeyMissing: code = "DPOP_KEY_MISSING"
case CredentialsManagerError.dpopKeyMismatch: code = "DPOP_KEY_MISMATCH"
case CredentialsManagerError.dpopNotConfigured: code = "DPOP_NOT_CONFIGURED"
default: code = "UNKNOWN"
}
return code
Expand Down
19 changes: 19 additions & 0 deletions src/core/models/CredentialsManagerError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ export const CredentialsManagerErrorCodes = {
INCOMPATIBLE_DEVICE: 'INCOMPATIBLE_DEVICE',
/** Cryptographic operation failed */
CRYPTO_EXCEPTION: 'CRYPTO_EXCEPTION',
/** DPoP key pair is no longer available in the device keystore/keychain - re-authentication required */
DPOP_KEY_MISSING: 'DPOP_KEY_MISSING',
/** Credentials are DPoP-bound but the client was not configured with DPoP */
DPOP_NOT_CONFIGURED: 'DPOP_NOT_CONFIGURED',
/** Current DPoP key pair does not match the one used when credentials were saved - re-authentication required */
DPOP_KEY_MISMATCH: 'DPOP_KEY_MISMATCH',
/** Unknown or uncategorized error */
UNKNOWN_ERROR: 'UNKNOWN_ERROR',
} as const;
Expand All @@ -80,6 +86,11 @@ const ERROR_CODE_MAP: Record<string, string> = {
NO_NETWORK: CredentialsManagerErrorCodes.NO_NETWORK,
API_ERROR: CredentialsManagerErrorCodes.API_ERROR,

// --- DPoP credential state errors ---
DPOP_KEY_MISSING: CredentialsManagerErrorCodes.DPOP_KEY_MISSING,
DPOP_NOT_CONFIGURED: CredentialsManagerErrorCodes.DPOP_NOT_CONFIGURED,
DPOP_KEY_MISMATCH: CredentialsManagerErrorCodes.DPOP_KEY_MISMATCH,

// --- API Credentials (MRRT) specific codes ---
API_EXCHANGE_FAILED: CredentialsManagerErrorCodes.API_EXCHANGE_FAILED,
// --- Web (@auth0/auth0-spa-js) mappings ---
Expand Down Expand Up @@ -175,6 +186,11 @@ const ERROR_CODE_MAP: Record<string, string> = {
* - `NO_NETWORK`: Network connectivity issue
* - `API_ERROR`: Generic API error
*
* ### DPoP Credential State:
* - `DPOP_KEY_MISSING`: DPoP key pair no longer available in keystore/keychain
* - `DPOP_NOT_CONFIGURED`: Credentials are DPoP-bound but client not configured with DPoP
* - `DPOP_KEY_MISMATCH`: Current DPoP key pair doesn't match the one used when credentials were saved
*
* ### Biometric Authentication:
* - `BIOMETRICS_FAILED`: Biometric authentication failed
* - `INCOMPATIBLE_DEVICE`: Device incompatible with secure storage
Expand Down Expand Up @@ -285,6 +301,9 @@ export class CredentialsManagerError extends AuthError {
* - `NO_REFRESH_TOKEN`: Refresh token is not available
* - `RENEW_FAILED`: Token renewal failed
* - `API_EXCHANGE_FAILED`: API credentials exchange failed (MRRT)
* - `DPOP_KEY_MISSING`: DPoP key pair no longer in keystore/keychain
* - `DPOP_NOT_CONFIGURED`: Credentials DPoP-bound but client not configured
* - `DPOP_KEY_MISMATCH`: DPoP key pair doesn't match saved credentials
* - `STORE_FAILED`: Failed to store credentials
* - `REVOKE_FAILED`: Failed to revoke refresh token
* - `LARGE_MIN_TTL`: Requested minimum TTL exceeds token lifetime
Expand Down
13 changes: 11 additions & 2 deletions src/core/models/__tests__/ErrorCodes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,20 @@ describe('Error Code Constants', () => {
'CRYPTO_EXCEPTION'
);
expect(CredentialsManagerErrorCodes.UNKNOWN_ERROR).toBe('UNKNOWN_ERROR');
expect(CredentialsManagerErrorCodes.DPOP_KEY_MISSING).toBe(
'DPOP_KEY_MISSING'
);
expect(CredentialsManagerErrorCodes.DPOP_NOT_CONFIGURED).toBe(
'DPOP_NOT_CONFIGURED'
);
expect(CredentialsManagerErrorCodes.DPOP_KEY_MISMATCH).toBe(
'DPOP_KEY_MISMATCH'
);
});

it('should have exactly 15 error codes', () => {
it('should have exactly 18 error codes', () => {
const keys = Object.keys(CredentialsManagerErrorCodes);
expect(keys).toHaveLength(15);
expect(keys).toHaveLength(18);
});

it('should be immutable (as const)', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,48 @@ describe('Common Error Handling - NativeCredentialsManager', () => {
});
});

describe('DPoP Credential State Errors', () => {
const dpopErrorTestCases = [
{
code: 'DPOP_KEY_MISSING',
message:
'The stored credentials are DPoP-bound but the DPoP key pair is no longer available in the keystore.',
expectedType: 'DPOP_KEY_MISSING',
},
{
code: 'DPOP_NOT_CONFIGURED',
message:
'The stored credentials are DPoP-bound but the client was not configured with DPoP.',
expectedType: 'DPOP_NOT_CONFIGURED',
},
{
code: 'DPOP_KEY_MISMATCH',
message:
'The stored credentials are DPoP-bound but the current DPoP key pair does not match the one used when credentials were saved.',
expectedType: 'DPOP_KEY_MISMATCH',
},
];

dpopErrorTestCases.forEach(({ code, message, expectedType }) => {
it(`should handle ${code} error`, async () => {
const nativeError = { code, message };
mockBridge.getCredentials.mockRejectedValue(nativeError);

await expect(manager.getCredentials()).rejects.toThrow(
CredentialsManagerError
);

try {
await manager.getCredentials();
} catch (e) {
const err = e as CredentialsManagerError;
expect(err.type).toBe(expectedType);
expect(err.message).toBe(message);
}
});
});
});

describe('Android Biometric Error Mappings', () => {
const biometricErrorTestCases = [
'BIOMETRIC_NO_ACTIVITY',
Expand Down
Loading