From 4a73548ca3ba032975a9f482a3b2079f4b13a8bd Mon Sep 17 00:00:00 2001 From: guabu <135956181+guabu@users.noreply.github.com> Date: Wed, 2 Apr 2025 09:09:25 +0200 Subject: [PATCH 01/50] feat: Support Turbo Modules (#1101) --- A0Auth0.podspec | 13 ++ README.md | 2 +- android/build.gradle | 19 ++ .../java/com/auth0/react/A0Auth0Module.java | 5 + ios/{A0Auth0.m => A0Auth0.mm} | 4 + ios/A0Auth0.xcodeproj/project.pbxproj | 8 +- package.json | 8 + .../__tests__/credentials-manager.spec.js | 88 ++++----- src/credentials-manager/index.ts | 22 +-- .../localAuthenticationOptions.ts | 12 +- src/internal-types.ts | 50 ----- src/specs/NativeA0Auth0.ts | 174 ++++++++++++++++++ src/specs/__mocks__/NativeA0Auth0.ts | 25 +++ src/utils/nativeHelper.ts | 4 +- src/webauth/__mocks__/react-native.js | 3 - .../__snapshots__/agent.spec.js.snap | 5 - src/webauth/__tests__/agent.spec.js | 143 ++++++-------- src/webauth/__tests__/webauth.spec.js | 5 +- src/webauth/agent.ts | 137 ++++++-------- 19 files changed, 420 insertions(+), 307 deletions(-) rename ios/{A0Auth0.m => A0Auth0.mm} (96%) create mode 100644 src/specs/NativeA0Auth0.ts create mode 100644 src/specs/__mocks__/NativeA0Auth0.ts delete mode 100644 src/webauth/__tests__/__snapshots__/agent.spec.js.snap diff --git a/A0Auth0.podspec b/A0Auth0.podspec index d8509b97..87653836 100644 --- a/A0Auth0.podspec +++ b/A0Auth0.podspec @@ -1,6 +1,7 @@ require 'json' package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' Pod::Spec.new do |s| s.name = 'A0Auth0' @@ -20,4 +21,16 @@ Pod::Spec.new do |s| s.dependency 'Auth0', '2.7.2' s.dependency 'JWTDecode', '3.1.0' s.dependency 'SimpleKeychain', '1.1.0' + + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + s.dependency "React-Codegen" + s.dependency "RCT-Folly" + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" end diff --git a/README.md b/README.md index dd2cbe1c..a06fdeb0 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ We're excited to announce the release of react-native-auth0 `v4.0.0`! Please not ### Requirements -This SDK targets apps that are using React Native SDK version `0.65.0` and up. If you're using an older React Native version, see the compatibility matrix below. +This SDK targets apps that are using React Native SDK version `0.76.0` and up. If you're using an older React Native version, see the compatibility matrix below. ### Platform compatibility diff --git a/android/build.gradle b/android/build.gradle index 002d60f6..614aed02 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -16,6 +16,7 @@ buildscript { apply plugin: 'com.android.library' apply plugin: 'maven-publish' +apply plugin: "com.facebook.react" // Matches values in recent template from React Native 0.62 // https://github.com/facebook/react-native/blob/0.62-stable/template/android/build.gradle#L5-L8 @@ -35,6 +36,9 @@ android { versionCode 1 versionName "1.0" } + buildFeatures { + buildConfig true + } lintOptions { abortOnError false } @@ -42,6 +46,15 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + + sourceSets { + main { + java.srcDirs += [ + "generated/java", + "generated/jni" + ] + } + } } repositories { @@ -129,3 +142,9 @@ afterEvaluate { project -> } } } + +react { + jsRootDir = file("../src/") + libraryName = "A0Auth0" + codegenJavaPackageName = "com.auth0.react" +} diff --git a/android/src/main/java/com/auth0/react/A0Auth0Module.java b/android/src/main/java/com/auth0/react/A0Auth0Module.java index aa6255e0..5537e395 100644 --- a/android/src/main/java/com/auth0/react/A0Auth0Module.java +++ b/android/src/main/java/com/auth0/react/A0Auth0Module.java @@ -84,6 +84,11 @@ public A0Auth0Module(ReactApplicationContext reactContext) { this.reactContext.addActivityEventListener(this); } + @ReactMethod + public String getBundleIdentifier() { + return reactContext.getApplicationInfo().packageName; + } + @ReactMethod public void initializeAuth0WithConfiguration(String clientId, String domain, ReadableMap localAuthenticationOptions, Promise promise) { this.auth0 = Auth0.getInstance(clientId, domain); diff --git a/ios/A0Auth0.m b/ios/A0Auth0.mm similarity index 96% rename from ios/A0Auth0.m rename to ios/A0Auth0.mm index 8a37ebdb..e5de7a20 100644 --- a/ios/A0Auth0.m +++ b/ios/A0Auth0.mm @@ -19,6 +19,10 @@ - (dispatch_queue_t)methodQueue RCT_EXPORT_MODULE(); +RCT_EXPORT_METHOD(getBundleIdentifier:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + resolve([[NSBundle mainBundle] bundleIdentifier]); +} + RCT_EXPORT_METHOD(hasValidAuth0InstanceWithConfiguration:(NSString *)clientId domain:(NSString *)domain resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { BOOL valid = [self checkHasValidNativeBridgeInstance:clientId domain:domain]; diff --git a/ios/A0Auth0.xcodeproj/project.pbxproj b/ios/A0Auth0.xcodeproj/project.pbxproj index 09aa4cc0..4c1b4646 100644 --- a/ios/A0Auth0.xcodeproj/project.pbxproj +++ b/ios/A0Auth0.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 2979802E2A3B572E0087BE80 /* NativeBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2979802D2A3B572E0087BE80 /* NativeBridge.swift */; }; - B3E7B58A1CC2AC0600A0062D /* A0Auth0.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* A0Auth0.m */; }; + B3E7B58A1CC2AC0600A0062D /* A0Auth0.mm in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* A0Auth0.mm */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -28,7 +28,7 @@ 2979802D2A3B572E0087BE80 /* NativeBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeBridge.swift; sourceTree = ""; }; A7107F0228A30FF7003A6450 /* A0Auth0-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "A0Auth0-Bridging-Header.h"; sourceTree = ""; }; B3E7B5881CC2AC0600A0062D /* A0Auth0.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = A0Auth0.h; sourceTree = ""; }; - B3E7B5891CC2AC0600A0062D /* A0Auth0.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = A0Auth0.m; sourceTree = ""; }; + B3E7B5891CC2AC0600A0062D /* A0Auth0.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = A0Auth0.mm; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -55,7 +55,7 @@ children = ( 2979802D2A3B572E0087BE80 /* NativeBridge.swift */, B3E7B5881CC2AC0600A0062D /* A0Auth0.h */, - B3E7B5891CC2AC0600A0062D /* A0Auth0.m */, + B3E7B5891CC2AC0600A0062D /* A0Auth0.mm */, 134814211AA4EA7D00B7C361 /* Products */, A7107F0228A30FF7003A6450 /* A0Auth0-Bridging-Header.h */, ); @@ -119,7 +119,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B3E7B58A1CC2AC0600A0062D /* A0Auth0.m in Sources */, + B3E7B58A1CC2AC0600A0062D /* A0Auth0.mm in Sources */, 2979802E2A3B572E0087BE80 /* NativeBridge.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/package.json b/package.json index b808f126..bea988bc 100644 --- a/package.json +++ b/package.json @@ -236,5 +236,13 @@ "hooks": { "pre-commit": "pretty-quick --staged" } + }, + "codegenConfig": { + "name": "RNAuth0Spec", + "type": "modules", + "jsSrcsDir": "src", + "android": { + "javaPackageName": "com.auth0.react" + } } } diff --git a/src/credentials-manager/__tests__/credentials-manager.spec.js b/src/credentials-manager/__tests__/credentials-manager.spec.js index 2788d796..404c3b9e 100644 --- a/src/credentials-manager/__tests__/credentials-manager.spec.js +++ b/src/credentials-manager/__tests__/credentials-manager.spec.js @@ -1,6 +1,9 @@ import CredentialsManager from '../index'; import CredentialsManagerError from '../credentialsManagerError'; -import { Platform } from 'react-native'; +import A0Auth0 from '../../specs/NativeA0Auth0'; + +// Mock the native module +jest.mock('../../specs/NativeA0Auth0'); describe('credentials manager tests', () => { const credentialsManager = new CredentialsManager( @@ -8,14 +11,6 @@ describe('credentials manager tests', () => { 'abc123' ); - credentialsManager.Auth0Module.hasValidAuth0InstanceWithConfiguration = () => - Promise.resolve(true); - credentialsManager.Auth0Module.saveCredentials = () => {}; - credentialsManager.Auth0Module.getCredentials = () => {}; - credentialsManager.Auth0Module.hasValidCredentials = () => {}; - credentialsManager.Auth0Module.clearCredentials = () => {}; - credentialsManager.Auth0Module.enableLocalAuthentication = () => {}; - const validToken = { idToken: '1234', accessToken: '1234', @@ -23,6 +18,20 @@ describe('credentials manager tests', () => { expiresAt: 1691603391, }; + beforeEach(() => { + // Reset all mocks before each test + jest.clearAllMocks(); + + // Set default implementation for native module functions + A0Auth0.hasValidAuth0InstanceWithConfiguration = jest.fn(() => + Promise.resolve(true) + ); + A0Auth0.saveCredentials = jest.fn(() => Promise.resolve(true)); + A0Auth0.getCredentials = jest.fn(() => Promise.resolve()); + A0Auth0.hasValidCredentials = jest.fn(() => Promise.resolve(false)); + A0Auth0.clearCredentials = jest.fn(() => Promise.resolve(true)); + }); + describe('test saving credentials', () => { it('throws when access token is empty', async () => { const testToken = Object.assign({}, validToken); @@ -65,108 +74,75 @@ describe('credentials manager tests', () => { }); it('proper error is thrown for exception', async () => { - const newNativeModule = jest - .spyOn( - credentialsManager.Auth0Module, - 'hasValidAuth0InstanceWithConfiguration' - ) - .mockImplementation(() => { - throw Error('123123'); - }); + A0Auth0.hasValidAuth0InstanceWithConfiguration = jest.fn(() => { + throw Error('123123'); + }); await expect( credentialsManager.saveCredentials(validToken) ).rejects.toThrow(); - newNativeModule.mockRestore(); }); it('succeeds for proper token', async () => { - const newNativeModule = jest - .spyOn(credentialsManager.Auth0Module, 'saveCredentials') - .mockImplementation(() => Promise.resolve(true)); + A0Auth0.saveCredentials = jest.fn(() => Promise.resolve(true)); await expect( credentialsManager.saveCredentials(validToken) ).resolves.toEqual(true); - newNativeModule.mockRestore(); }); }); describe('test getting credentials', () => { it('proper error is thrown for exception', async () => { - const newNativeModule = jest - .spyOn(credentialsManager.Auth0Module, 'getCredentials') - .mockImplementation(() => { - throw Error('123123'); - }); + A0Auth0.getCredentials = jest.fn(() => { + throw Error('123123'); + }); await expect(credentialsManager.getCredentials()).rejects.toThrow(); - newNativeModule.mockRestore(); }); it('succeedsfully returns credentials', async () => { - const newNativeModule = jest - .spyOn(credentialsManager.Auth0Module, 'getCredentials') - .mockImplementation(() => Promise.resolve(validToken)); + A0Auth0.getCredentials = jest.fn(() => Promise.resolve(validToken)); await expect(credentialsManager.getCredentials()).resolves.toEqual( validToken ); - newNativeModule.mockRestore(); }); it('passes along the forceRefresh parameter', async () => { - const newNativeModule = jest - .spyOn(credentialsManager.Auth0Module, 'getCredentials') - .mockImplementation(() => Promise.resolve(validToken)); + A0Auth0.getCredentials = jest.fn(() => Promise.resolve(validToken)); await credentialsManager.getCredentials(null, 0, {}, true); - expect( - credentialsManager.Auth0Module.getCredentials - ).toHaveBeenCalledWith(null, 0, {}, true); - - newNativeModule.mockRestore(); + expect(A0Auth0.getCredentials).toHaveBeenCalledWith(null, 0, {}, true); }); }); describe('test hasValidCredentials', () => { it('returns false', async () => { - const newNativeModule = jest - .spyOn(credentialsManager.Auth0Module, 'hasValidCredentials') - .mockImplementation(() => Promise.resolve(true)); + A0Auth0.hasValidCredentials = jest.fn(() => Promise.resolve(true)); await expect(credentialsManager.hasValidCredentials()).resolves.toEqual( true ); - newNativeModule.mockRestore(); }); it('returns true', async () => { - const newNativeModule = jest - .spyOn(credentialsManager.Auth0Module, 'hasValidCredentials') - .mockImplementation(() => Promise.resolve(true)); + A0Auth0.hasValidCredentials = jest.fn(() => Promise.resolve(true)); await expect(credentialsManager.hasValidCredentials()).resolves.toEqual( true ); - newNativeModule.mockRestore(); }); }); describe('test clearing credentials', () => { it('returns false', async () => { - const newNativeModule = jest - .spyOn(credentialsManager.Auth0Module, 'clearCredentials') - .mockImplementation(() => Promise.resolve(true)); + A0Auth0.clearCredentials = jest.fn(() => Promise.resolve(true)); await expect(credentialsManager.clearCredentials()).resolves.toEqual( true ); - newNativeModule.mockRestore(); }); it('returns true', async () => { - const newNativeModule = jest - .spyOn(credentialsManager.Auth0Module, 'clearCredentials') - .mockImplementation(() => Promise.resolve(true)); + A0Auth0.clearCredentials = jest.fn(() => Promise.resolve(true)); await expect(credentialsManager.clearCredentials()).resolves.toEqual( true ); - newNativeModule.mockRestore(); }); }); diff --git a/src/credentials-manager/index.ts b/src/credentials-manager/index.ts index 90e3eee6..08dcaa78 100644 --- a/src/credentials-manager/index.ts +++ b/src/credentials-manager/index.ts @@ -1,14 +1,13 @@ -import { NativeModules } from 'react-native'; import CredentialsManagerError from './credentialsManagerError'; import type { Credentials } from '../types'; -import type { Auth0Module, NativeModuleError } from '../internal-types'; import { _ensureNativeModuleIsInitializedWithConfiguration } from '../utils/nativeHelper'; import type { LocalAuthenticationOptions } from './localAuthenticationOptions'; +import A0Auth0 from '../specs/NativeA0Auth0'; +import type { NativeModuleError } from '../internal-types'; class CredentialsManager { private domain; private clientId; - private Auth0Module: Auth0Module; private localAuthenticationOptions?: LocalAuthenticationOptions; /** @@ -22,7 +21,6 @@ class CredentialsManager { this.domain = domain; this.clientId = clientId; this.localAuthenticationOptions = localAuthenticationOptions; - this.Auth0Module = NativeModules.A0Auth0; } /** @@ -42,12 +40,12 @@ class CredentialsManager { }); try { await _ensureNativeModuleIsInitializedWithConfiguration( - this.Auth0Module, + A0Auth0, this.clientId, this.domain, this.localAuthenticationOptions ); - return await this.Auth0Module.saveCredentials(credentials); + return await A0Auth0.saveCredentials(credentials); } catch (e) { const json = { error: 'a0.credential_manager.invalid', @@ -75,13 +73,13 @@ class CredentialsManager { ): Promise { try { await _ensureNativeModuleIsInitializedWithConfiguration( - this.Auth0Module, + A0Auth0, this.clientId, this.domain, this.localAuthenticationOptions ); return new Promise((resolve, reject) => { - this.Auth0Module.getCredentials(scope, minTtl, parameters, forceRefresh) + A0Auth0.getCredentials(scope, minTtl, parameters, forceRefresh) .then(resolve) .catch((e: NativeModuleError) => { const json = { @@ -109,12 +107,12 @@ class CredentialsManager { */ async hasValidCredentials(minTtl = 0): Promise { await _ensureNativeModuleIsInitializedWithConfiguration( - this.Auth0Module, + A0Auth0, this.clientId, this.domain, this.localAuthenticationOptions ); - return await this.Auth0Module.hasValidCredentials(minTtl); + return await A0Auth0.hasValidCredentials(minTtl); } /** @@ -122,12 +120,12 @@ class CredentialsManager { */ async clearCredentials(): Promise { await _ensureNativeModuleIsInitializedWithConfiguration( - this.Auth0Module, + A0Auth0, this.clientId, this.domain, this.localAuthenticationOptions ); - return this.Auth0Module.clearCredentials(); + return A0Auth0.clearCredentials(); } } diff --git a/src/credentials-manager/localAuthenticationOptions.ts b/src/credentials-manager/localAuthenticationOptions.ts index c0df7ca0..8b77dbab 100644 --- a/src/credentials-manager/localAuthenticationOptions.ts +++ b/src/credentials-manager/localAuthenticationOptions.ts @@ -9,19 +9,19 @@ interface LocalAuthenticationOptions { /** * The title of the authentication prompt. **Applicable for both Android and iOS**. */ - title: String; + title: string; /** * The subtitle of the authentication prompt. **Applicable for Android only.** */ - subtitle?: String; + subtitle?: string; /** * The description of the authentication prompt. **Applicable for Android only.** */ - description?: String; + description?: string; /** * The cancel button title of the authentication prompt. **Applicable for both Android and iOS.** */ - cancelTitle?: String; + cancelTitle?: string; /** * The evaluation policy to use when prompting the user for authentication. Defaults to LocalAuthenticationStrategy.deviceOwnerWithBiometrics. **Applicable for iOS only.** */ @@ -29,7 +29,7 @@ interface LocalAuthenticationOptions { /** * The fallback button title of the authentication prompt. **Applicable for iOS only.** */ - fallbackTitle?: String; + fallbackTitle?: string; /** * The authentication level to use when prompting the user for authentication. Defaults to LocalAuthenticationLevel.strong. **Applicable for Android only.** */ @@ -37,7 +37,7 @@ interface LocalAuthenticationOptions { /** * Should the user be given the option to authenticate with their device PIN, pattern, or password instead of a biometric. **Applicable for Android only.** */ - deviceCredentialFallback?: Boolean; + deviceCredentialFallback?: boolean; } export type { LocalAuthenticationOptions }; diff --git a/src/internal-types.ts b/src/internal-types.ts index beeef95a..6a9a3518 100644 --- a/src/internal-types.ts +++ b/src/internal-types.ts @@ -1,6 +1,4 @@ import type { JwtPayload } from 'jwt-decode'; -import type { Credentials } from './types'; -import type { LocalAuthenticationOptions } from './credentials-manager/localAuthenticationOptions'; export type CredentialsResponse = { id_token: string; @@ -69,54 +67,6 @@ export type RawMultifactorChallengeResponse = export type CustomJwtPayload = JwtPayload & RawUser; -/** - * Type representing the Native Auth0 API's on iOS and Android - */ -export type Auth0Module = { - bundleIdentifier: string; - webAuth: ( - scheme: string, - redirectUri: string, - state?: string, - nonce?: string, - audience?: string, - scope?: string, - connection?: string, - maxAge?: number, - organization?: string, - invitationUrl?: string, - leeway?: number, - ephemeralSession?: boolean, - safariViewControllerPresentationStyle?: number, - additionalParameters?: { [key: string]: string } - ) => Promise; - webAuthLogout: ( - scheme: string, - federated: boolean, - redirectUri: string - ) => Promise; - resumeWebAuth: (url: string) => Promise; - cancelWebAuth: () => Promise; - saveCredentials: (credentials: Credentials) => Promise; - getCredentials: ( - scope?: string, - minTtl?: number, - parameters?: Record, - forceRefresh?: boolean - ) => Promise; - hasValidCredentials: (minTtl?: number) => Promise; - clearCredentials: () => Promise; - hasValidAuth0InstanceWithConfiguration: ( - clientId: String, - domain: String - ) => Promise; - initializeAuth0WithConfiguration: ( - clientId: string, - domain: string, - localAuthenticationOptions?: LocalAuthenticationOptions - ) => Promise; -}; - export type AgentParameters = { clientId: string; domain: string; diff --git a/src/specs/NativeA0Auth0.ts b/src/specs/NativeA0Auth0.ts new file mode 100644 index 00000000..b50a85ef --- /dev/null +++ b/src/specs/NativeA0Auth0.ts @@ -0,0 +1,174 @@ +import type { TurboModule } from 'react-native'; +import { TurboModuleRegistry } from 'react-native'; + +export interface Spec extends TurboModule { + /** + * Get the bundle identifier + */ + getBundleIdentifier(): Promise; + + /** + * Check if the Auth0 instance is valid with the given configuration + */ + hasValidAuth0InstanceWithConfiguration( + clientId: string, + domain: string + ): Promise; + + /** + * Initialize Auth0 with the given configuration + */ + initializeAuth0WithConfiguration( + clientId: string, + domain: string, + localAuthenticationOptions?: LocalAuthenticationOptions + ): Promise; + + /** + * Save credentials + */ + saveCredentials(credentials: Credentials): Promise; + + /** + * Get credentials with the given scope + */ + getCredentials( + scope: string | undefined, + minTTL: number, + parameters: Object, + forceRefresh: boolean + ): Promise; + + /** + * Check if there are valid credentials + */ + hasValidCredentials(minTTL: number): Promise; + + /** + * Clear credentials + */ + clearCredentials(): Promise; + + /** + * Enable local authentication + */ + enableLocalAuthentication( + title: string, + cancelTitle: string, + fallbackTitle: string, + evaluationPolicy: number + ): void; + + /** + * Start web authentication + */ + webAuth( + scheme: string, + redirectUri: string, + state?: string, + nonce?: string, + audience?: string, + scope?: string, + connection?: string, + maxAge?: number, + organization?: string, + invitationUrl?: string, + leeway?: number, + ephemeralSession?: boolean, + safariViewControllerPresentationStyle?: number, + additionalParameters?: { [key: string]: string } + ): Promise; + + /** + * Logout from web authentication + */ + webAuthLogout( + scheme: string, + federated: boolean, + redirectUri: string + ): Promise; + + /** + * Resume web authentication + */ + resumeWebAuth(url: string): Promise; + + /** + * Cancel web authentication + */ + cancelWebAuth(): Promise; +} + +export default TurboModuleRegistry.getEnforcing('A0Auth0'); + +interface CredentialsResponse { + id_token: string; + access_token: string; + token_type: string; + expires_in: number; + refresh_token?: string; + scope?: string; + [key: string]: any; +} + +interface LocalAuthenticationOptions { + /** + * The title of the authentication prompt. **Applicable for both Android and iOS**. + */ + title: string; + /** + * The subtitle of the authentication prompt. **Applicable for Android only.** + */ + subtitle?: string; + /** + * The description of the authentication prompt. **Applicable for Android only.** + */ + description?: string; + /** + * The cancel button title of the authentication prompt. **Applicable for both Android and iOS.** + */ + cancelTitle?: string; + /** + * The evaluation policy to use when prompting the user for authentication. Defaults to LocalAuthenticationStrategy.deviceOwnerWithBiometrics. **Applicable for iOS only.** + */ + evaluationPolicy?: number; + /** + * The fallback button title of the authentication prompt. **Applicable for iOS only.** + */ + fallbackTitle?: string; + /** + * The authentication level to use when prompting the user for authentication. Defaults to LocalAuthenticationLevel.strong. **Applicable for Android only.** + */ + authenticationLevel?: number; + /** + * Should the user be given the option to authenticate with their device PIN, pattern, or password instead of a biometric. **Applicable for Android only.** + */ + deviceCredentialFallback?: boolean; +} + +interface Credentials { + /** + * A token in JWT format that has user claims + */ + idToken: string; + /** + * The token used to make API calls + */ + accessToken: string; + /** + * The type of the token, e.g.: Bearer + */ + tokenType: string; + /** + * Used to denote when the token will expire, as a UNIX timestamp + */ + expiresAt: number; + /** + * The token used to refresh the access token + */ + refreshToken?: string; + /** + * Represents the scope of the current token + */ + scope?: string; +} diff --git a/src/specs/__mocks__/NativeA0Auth0.ts b/src/specs/__mocks__/NativeA0Auth0.ts new file mode 100644 index 00000000..fd9565f9 --- /dev/null +++ b/src/specs/__mocks__/NativeA0Auth0.ts @@ -0,0 +1,25 @@ +// Mock implementation of the A0Auth0 native module for testing +const A0Auth0 = { + hasValidAuth0InstanceWithConfiguration: jest.fn(() => Promise.resolve(true)), + initializeAuth0WithConfiguration: jest.fn(() => Promise.resolve()), + saveCredentials: jest.fn(() => Promise.resolve(true)), + getCredentials: jest.fn(() => Promise.resolve()), + hasValidCredentials: jest.fn(() => Promise.resolve(false)), + clearCredentials: jest.fn(() => Promise.resolve(true)), + enableLocalAuthentication: jest.fn(() => Promise.resolve()), + getBundleIdentifier: jest.fn(() => Promise.resolve('com.my.app')), + webAuth: jest.fn(() => + Promise.resolve({ + id_token: 'id_token', + access_token: 'access_token', + token_type: 'Bearer', + expires_in: 86400, + refresh_token: 'refresh_token', + }) + ), + webAuthLogout: jest.fn(() => Promise.resolve()), + cancelWebAuth: jest.fn(() => Promise.resolve()), + resumeWebAuth: jest.fn(() => Promise.resolve()), +}; + +export default A0Auth0; diff --git a/src/utils/nativeHelper.ts b/src/utils/nativeHelper.ts index e19f83ac..550dd815 100644 --- a/src/utils/nativeHelper.ts +++ b/src/utils/nativeHelper.ts @@ -1,9 +1,9 @@ +import type { Spec } from '../specs/NativeA0Auth0'; import type { LocalAuthenticationOptions } from '../credentials-manager/localAuthenticationOptions'; -import type { Auth0Module } from '../internal-types'; //private export async function _ensureNativeModuleIsInitializedWithConfiguration( - nativeModule: Auth0Module, + nativeModule: Spec, clientId: string, domain: string, localAuthenticationOptions?: LocalAuthenticationOptions diff --git a/src/webauth/__mocks__/react-native.js b/src/webauth/__mocks__/react-native.js index 5e98818d..9db0dcbb 100644 --- a/src/webauth/__mocks__/react-native.js +++ b/src/webauth/__mocks__/react-native.js @@ -1,8 +1,5 @@ -import A0Auth0 from './auth0'; - const mock = {}; -mock.NativeModules = { A0Auth0: new A0Auth0() }; mock.Platform = { OS: 'test-os' }; module.exports = mock; diff --git a/src/webauth/__tests__/__snapshots__/agent.spec.js.snap b/src/webauth/__tests__/__snapshots__/agent.spec.js.snap deleted file mode 100644 index af747d8c..00000000 --- a/src/webauth/__tests__/__snapshots__/agent.spec.js.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Agent login should fail if native module is not linked 1`] = `[Error: Missing NativeModule. React Native versions 0.60 and up perform auto-linking. Please see https://github.com/react-native-community/cli/blob/master/docs/autolinking.md.]`; - -exports[`Agent logout should fail if native module is not linked 1`] = `[Error: Missing NativeModule. React Native versions 0.60 and up perform auto-linking. Please see https://github.com/react-native-community/cli/blob/master/docs/autolinking.md.]`; \ No newline at end of file diff --git a/src/webauth/__tests__/agent.spec.js b/src/webauth/__tests__/agent.spec.js index 7bd76387..f916d866 100644 --- a/src/webauth/__tests__/agent.spec.js +++ b/src/webauth/__tests__/agent.spec.js @@ -1,6 +1,7 @@ import * as nativeUtils from '../../utils/nativeHelper'; import Agent from '../agent'; -import { NativeModules, Platform, Linking } from 'react-native'; +import { Platform, Linking } from 'react-native'; +import A0Auth0 from '../../specs/NativeA0Auth0'; const localAuthenticationOptions = { title: 'Authenticate With Your Biometrics', @@ -8,24 +9,15 @@ const localAuthenticationOptions = { authenticationLevel: 0, }; +// Mock the native module +jest.mock('../../specs/NativeA0Auth0'); + jest.mock('react-native', () => { - // Require the original module to not be mocked... return { __esModule: true, // Use it when dealing with esModules Linking: { addEventListener: jest.fn(), }, - NativeModules: { - A0Auth0: { - webAuth: () => {}, - webAuthLogout: () => {}, - cancelWebAuth: () => {}, - resumeWebAuth: () => {}, - hasValidAuth0Instance: () => {}, - initializeAuth0: () => {}, - bundleIdentifier: 'com.my.app', - }, - }, Platform: { OS: 'ios', }, @@ -35,31 +27,30 @@ jest.mock('react-native', () => { describe('Agent', () => { const agent = new Agent(); - afterEach(() => { + beforeEach(() => { + // Reset all mocks before each test jest.clearAllMocks(); + + // Set default implementation for native module functions + A0Auth0.hasValidAuth0InstanceWithConfiguration = jest.fn(() => + Promise.resolve(true) + ); + A0Auth0.initializeAuth0WithConfiguration = jest.fn(() => Promise.resolve()); + A0Auth0.getBundleIdentifier = jest.fn(() => Promise.resolve('com.my.app')); + A0Auth0.webAuth = jest.fn(() => Promise.resolve(true)); + A0Auth0.webAuthLogout = jest.fn(() => Promise.resolve(true)); + A0Auth0.cancelWebAuth = jest.fn(() => Promise.resolve(true)); + A0Auth0.resumeWebAuth = jest.fn(() => Promise.resolve(true)); }); describe('login', () => { - it('should fail if native module is not linked', async () => { - const replacedProperty = jest.replaceProperty( - NativeModules, - 'A0Auth0', - undefined - ); - expect.assertions(1); - await expect(agent.login()).rejects.toMatchSnapshot(); - replacedProperty.restore(); - }); - it('should ensure module is initialized', async () => { let domain = 'test.com'; let clientId = 'client id value'; const mock = jest .spyOn(nativeUtils, '_ensureNativeModuleIsInitializedWithConfiguration') .mockImplementation(() => Promise.resolve(true)); - jest - .spyOn(NativeModules.A0Auth0, 'webAuth') - .mockImplementation(() => Promise.resolve(true)); + agent.login( { clientId: clientId, @@ -69,7 +60,7 @@ describe('Agent', () => { localAuthenticationOptions ); expect(mock).toBeCalledWith( - NativeModules.A0Auth0, + A0Auth0, clientId, domain, localAuthenticationOptions @@ -82,9 +73,7 @@ describe('Agent', () => { const mock = jest .spyOn(nativeUtils, '_ensureNativeModuleIsInitializedWithConfiguration') .mockImplementation(() => Promise.resolve(true)); - const mockLogin = jest - .spyOn(NativeModules.A0Auth0, 'webAuth') - .mockImplementation(() => Promise.resolve(true)); + await agent.login( { clientId: clientId, @@ -108,12 +97,12 @@ describe('Agent', () => { localAuthenticationOptions ); expect(mock).toBeCalledWith( - NativeModules.A0Auth0, + A0Auth0, clientId, domain, localAuthenticationOptions ); - expect(mockLogin).toBeCalledWith( + expect(A0Auth0.webAuth).toBeCalledWith( 'test', 'test://test.com/ios/com.my.app/callback', 'state', @@ -137,9 +126,7 @@ describe('Agent', () => { const mock = jest .spyOn(nativeUtils, '_ensureNativeModuleIsInitializedWithConfiguration') .mockImplementation(() => Promise.resolve(true)); - const mockLogin = jest - .spyOn(NativeModules.A0Auth0, 'webAuth') - .mockImplementation(() => Promise.resolve(true)); + await agent.login( { clientId: clientId, @@ -151,12 +138,12 @@ describe('Agent', () => { localAuthenticationOptions ); expect(mock).toBeCalledWith( - NativeModules.A0Auth0, + A0Auth0, clientId, domain, localAuthenticationOptions ); - expect(mockLogin).toBeCalledWith( + expect(A0Auth0.webAuth).toBeCalledWith( 'com.my.app.auth0', 'redirect://redirect.com', undefined, @@ -176,24 +163,14 @@ describe('Agent', () => { }); describe('logout', () => { - it('should fail if native module is not linked', async () => { - const replacedProperty = jest.replaceProperty( - NativeModules, - 'A0Auth0', - undefined - ); - expect.assertions(1); - await expect(agent.logout()).rejects.toMatchSnapshot(); - replacedProperty.restore(); - }); - it('should ensure module is initialized', async () => { let domain = 'test.com'; let clientId = 'client id value'; const mock = jest .spyOn(nativeUtils, '_ensureNativeModuleIsInitializedWithConfiguration') .mockImplementation(() => Promise.resolve(true)); - agent.logout( + + await agent.logout( { clientId: clientId, domain: domain, @@ -201,8 +178,8 @@ describe('Agent', () => { { customScheme: 'test' }, localAuthenticationOptions ); - expect(mock).toBeCalledWith( - NativeModules.A0Auth0, + expect(mock).toHaveBeenCalledWith( + A0Auth0, clientId, domain, localAuthenticationOptions @@ -215,9 +192,7 @@ describe('Agent', () => { const mock = jest .spyOn(nativeUtils, '_ensureNativeModuleIsInitializedWithConfiguration') .mockImplementation(() => Promise.resolve(true)); - const mockLogin = jest - .spyOn(NativeModules.A0Auth0, 'webAuthLogout') - .mockImplementation(() => Promise.resolve(true)); + await agent.logout( { clientId: clientId, @@ -230,12 +205,12 @@ describe('Agent', () => { localAuthenticationOptions ); expect(mock).toBeCalledWith( - NativeModules.A0Auth0, + A0Auth0, clientId, domain, localAuthenticationOptions ); - expect(mockLogin).toBeCalledWith( + expect(A0Auth0.webAuthLogout).toBeCalledWith( 'test', true, 'test://test.com/ios/com.my.app/callback' @@ -248,9 +223,7 @@ describe('Agent', () => { const mock = jest .spyOn(nativeUtils, '_ensureNativeModuleIsInitializedWithConfiguration') .mockImplementation(() => Promise.resolve(true)); - const mockLogin = jest - .spyOn(NativeModules.A0Auth0, 'webAuthLogout') - .mockImplementation(() => Promise.resolve(true)); + await agent.logout( { clientId: clientId, @@ -262,12 +235,12 @@ describe('Agent', () => { localAuthenticationOptions ); expect(mock).toBeCalledWith( - NativeModules.A0Auth0, + A0Auth0, clientId, domain, localAuthenticationOptions ); - expect(mockLogin).toBeCalledWith( + expect(A0Auth0.webAuthLogout).toBeCalledWith( 'com.my.app.auth0', false, 'redirect://redirect.com' @@ -292,7 +265,7 @@ describe('Agent', () => { ); expect(mock).toBeCalledWith( - NativeModules.A0Auth0, + A0Auth0, clientId, domain, localAuthenticationOptions @@ -305,9 +278,7 @@ describe('Agent', () => { const mock = jest .spyOn(nativeUtils, '_ensureNativeModuleIsInitializedWithConfiguration') .mockImplementation(() => Promise.resolve(true)); - const mockCancelWebAuth = jest - .spyOn(NativeModules.A0Auth0, 'cancelWebAuth') - .mockImplementation(() => Promise.resolve(true)); + await agent.cancelWebAuth( { clientId: clientId, @@ -316,43 +287,47 @@ describe('Agent', () => { localAuthenticationOptions ); expect(mock).toBeCalledWith( - NativeModules.A0Auth0, + A0Auth0, clientId, domain, localAuthenticationOptions ); - expect(mockCancelWebAuth).toHaveBeenCalled(); + expect(A0Auth0.cancelWebAuth).toHaveBeenCalled(); }); }); describe('getScheme', () => { it('should return custom scheme', async () => { - await expect(agent.getScheme(false, 'custom')).toEqual('custom'); + expect(await agent.getScheme(false, 'custom')).toEqual('custom'); }); it('should return custom scheme even if legacy behaviour set to true', async () => { - await expect(agent.getScheme(true, 'custom')).toEqual('custom'); + expect(await agent.getScheme(true, 'custom')).toEqual('custom'); }); it('should return bundle identifier', async () => { - NativeModules.A0Auth0.bundleIdentifier = 'com.test'; - await expect(agent.getScheme()).toEqual('com.test.auth0'); + A0Auth0.getBundleIdentifier = jest.fn(() => Promise.resolve('com.test')); + await expect(agent.getScheme()).resolves.toEqual('com.test.auth0'); + expect(A0Auth0.getBundleIdentifier).toHaveBeenCalled(); }); it('should return bundle identifier lower cased', async () => { - NativeModules.A0Auth0.bundleIdentifier = 'com.Test'; - await expect(agent.getScheme()).toEqual('com.test.auth0'); + A0Auth0.getBundleIdentifier = jest.fn(() => Promise.resolve('com.Test')); + await expect(agent.getScheme()).resolves.toEqual('com.test.auth0'); + expect(A0Auth0.getBundleIdentifier).toHaveBeenCalled(); }); it('should return legacy scheme', async () => { - NativeModules.A0Auth0.bundleIdentifier = 'com.Test'; - await expect(agent.getScheme(true)).toEqual('com.test'); + A0Auth0.getBundleIdentifier = jest.fn(() => Promise.resolve('com.Test')); + await expect(agent.getScheme(true)).resolves.toEqual('com.test'); + expect(A0Auth0.getBundleIdentifier).toHaveBeenCalled(); }); }); describe('callbackUri', () => { it('should return callback uri with given domain and scheme', async () => { - await expect(agent.callbackUri('domain', 'scheme')).toEqual( + A0Auth0.getBundleIdentifier = jest.fn(() => Promise.resolve('com.test')); + await expect(agent.callbackUri('domain', 'scheme')).resolves.toEqual( 'scheme://domain/ios/com.test/callback' ); }); @@ -399,20 +374,18 @@ describe('Agent', () => { .spyOn(nativeUtils, '_ensureNativeModuleIsInitializedWithConfiguration') .mockImplementationOnce(() => {}); - jest.spyOn(NativeModules.A0Auth0, 'webAuth').mockImplementation(() => { + A0Auth0.webAuth = jest.fn(() => { mockEventListener.mock.calls[0][1]({ url: 'https://callback.url.com' }); - Promise.resolve(true); + return Promise.resolve(true); }); - jest - .spyOn(NativeModules.A0Auth0, 'resumeWebAuth') - .mockImplementation(() => Promise.resolve(true)); + A0Auth0.resumeWebAuth = jest.fn(() => Promise.resolve(true)); await agent.login({}, { safariViewControllerPresentationStyle: 0 }); expect(Linking.addEventListener).toHaveBeenCalledTimes(1); - expect(NativeModules.A0Auth0.resumeWebAuth).toHaveBeenCalledTimes(1); + expect(A0Auth0.resumeWebAuth).toHaveBeenCalledTimes(1); expect(mockEventListener.mock.calls[0][0]).toEqual('url'); - expect(NativeModules.A0Auth0.resumeWebAuth).toHaveBeenCalledWith( + expect(A0Auth0.resumeWebAuth).toHaveBeenCalledWith( 'https://callback.url.com' ); expect(mockSubscription.remove).toHaveBeenCalledTimes(1); diff --git a/src/webauth/__tests__/webauth.spec.js b/src/webauth/__tests__/webauth.spec.js index 45092a45..01ef7cb4 100644 --- a/src/webauth/__tests__/webauth.spec.js +++ b/src/webauth/__tests__/webauth.spec.js @@ -1,10 +1,9 @@ jest.mock('react-native'); import Auth from '../../auth'; import WebAuth from '../index'; -import { NativeModules } from 'react-native'; -import { URL } from 'url'; -const A0Auth0 = NativeModules.A0Auth0; +// Mock the native module +jest.mock('../../specs/NativeA0Auth0'); describe('WebAuth', () => { const clientId = 'abc123'; diff --git a/src/webauth/agent.ts b/src/webauth/agent.ts index d2beed05..9bec006d 100644 --- a/src/webauth/agent.ts +++ b/src/webauth/agent.ts @@ -1,16 +1,14 @@ -import { NativeModules, Platform, Linking } from 'react-native'; -import type { EmitterSubscription } from 'react-native'; +import { Platform, Linking, type EmitterSubscription } from 'react-native'; import type { Credentials } from '../types'; import { _ensureNativeModuleIsInitializedWithConfiguration } from '../utils/nativeHelper'; import type { AgentLoginOptions, AgentLogoutOptions, AgentParameters, - Auth0Module, } from '../internal-types'; import type { LocalAuthenticationOptions } from '../credentials-manager/localAuthenticationOptions'; +import NativeA0Auth0 from '../specs/NativeA0Auth0'; -const A0Auth0: Auth0Module = NativeModules.A0Auth0; class Agent { async login( parameters: AgentParameters, @@ -18,80 +16,63 @@ class Agent { localAuthenticationOptions?: LocalAuthenticationOptions ): Promise { let linkSubscription: EmitterSubscription | null = null; - if (!NativeModules.A0Auth0) { - return Promise.reject( - new Error( - 'Missing NativeModule. React Native versions 0.60 and up perform auto-linking. Please see https://github.com/react-native-community/cli/blob/master/docs/autolinking.md.' - ) + if (Platform.OS === 'ios') { + linkSubscription = Linking.addEventListener('url', async (event) => { + linkSubscription?.remove(); + await NativeA0Auth0.resumeWebAuth(event.url); + }); + } + + try { + await _ensureNativeModuleIsInitializedWithConfiguration( + NativeA0Auth0, + parameters.clientId, + parameters.domain, + localAuthenticationOptions ); + let scheme = await this.getScheme( + options.useLegacyCallbackUrl ?? false, + options.customScheme + ); + let redirectUri = + options.redirectUrl ?? + (await this.callbackUri(parameters.domain, scheme)); + let credentials = await NativeA0Auth0.webAuth( + scheme, + redirectUri, + options.state, + options.nonce, + options.audience, + options.scope, + options.connection, + options.maxAge ?? 0, + options.organization, + options.invitationUrl, + options.leeway ?? 0, + options.ephemeralSession ?? false, + options.safariViewControllerPresentationStyle ?? 99, //Since we can't pass null to the native layer, and we need a value to represent this parameter is not set, we are using 99. + //The native layer will check for this and ignore if the value is 99 + options.additionalParameters ?? {} + ); + + return credentials as unknown as Credentials; + } catch (error) { + linkSubscription?.remove(); + throw error; } - return new Promise(async (resolve, reject) => { - if (Platform.OS === 'ios') { - linkSubscription = Linking.addEventListener('url', async (event) => { - try { - linkSubscription?.remove(); - await A0Auth0.resumeWebAuth(event.url); - } catch (error) { - reject(error); - } - }); - } - try { - await _ensureNativeModuleIsInitializedWithConfiguration( - A0Auth0, - parameters.clientId, - parameters.domain, - localAuthenticationOptions - ); - let scheme = this.getScheme( - options.useLegacyCallbackUrl ?? false, - options.customScheme - ); - let redirectUri = - options.redirectUrl ?? this.callbackUri(parameters.domain, scheme); - let credentials = await A0Auth0.webAuth( - scheme, - redirectUri, - options.state, - options.nonce, - options.audience, - options.scope, - options.connection, - options.maxAge ?? 0, - options.organization, - options.invitationUrl, - options.leeway ?? 0, - options.ephemeralSession ?? false, - options.safariViewControllerPresentationStyle ?? 99, //Since we can't pass null to the native layer, and we need a value to represent this parameter is not set, we are using 99. - //The native layer will check for this and ignore if the value is 99 - options.additionalParameters ?? {} - ); - resolve(credentials); - } catch (error) { - linkSubscription?.remove(); - reject(error); - } - }); } async cancelWebAuth( parameters: AgentParameters, localAuthenticationOptions?: LocalAuthenticationOptions ): Promise { - if (!NativeModules.A0Auth0) { - return Promise.reject( - new Error( - 'Missing NativeModule. React Native versions 0.60 and up perform auto-linking. Please see https://github.com/react-native-community/cli/blob/master/docs/autolinking.md.' - ) - ); - } await _ensureNativeModuleIsInitializedWithConfiguration( - NativeModules.A0Auth0, + NativeA0Auth0, parameters.clientId, parameters.domain, localAuthenticationOptions ); - return A0Auth0.cancelWebAuth(); + return NativeA0Auth0.cancelWebAuth(); } async logout( @@ -99,42 +80,38 @@ class Agent { options: AgentLogoutOptions, localAuthenticationOptions?: LocalAuthenticationOptions ): Promise { - if (!NativeModules.A0Auth0) { - return Promise.reject( - new Error( - 'Missing NativeModule. React Native versions 0.60 and up perform auto-linking. Please see https://github.com/react-native-community/cli/blob/master/docs/autolinking.md.' - ) - ); - } let federated = options.federated ?? false; - let scheme = this.getScheme( + let scheme = await this.getScheme( options.useLegacyCallbackUrl ?? false, options.customScheme ); let redirectUri = - options.returnToUrl ?? this.callbackUri(parameters.domain, scheme); + options.returnToUrl ?? + (await this.callbackUri(parameters.domain, scheme)); await _ensureNativeModuleIsInitializedWithConfiguration( - NativeModules.A0Auth0, + NativeA0Auth0, parameters.clientId, parameters.domain, localAuthenticationOptions ); - return A0Auth0.webAuthLogout(scheme, federated, redirectUri); + return NativeA0Auth0.webAuthLogout(scheme, federated, redirectUri); } - private getScheme( + private async getScheme( useLegacyCustomSchemeBehaviour: boolean, customScheme?: string ) { - let scheme = NativeModules.A0Auth0.bundleIdentifier.toLowerCase(); + let scheme = (await NativeA0Auth0.getBundleIdentifier()).toLowerCase(); if (!useLegacyCustomSchemeBehaviour) { scheme = scheme + '.auth0'; } return customScheme ?? scheme; } - private callbackUri(domain: string, scheme: string) { - let bundleIdentifier = NativeModules.A0Auth0.bundleIdentifier.toLowerCase(); + private async callbackUri(domain: string, scheme: string) { + let bundleIdentifier = ( + await NativeA0Auth0.getBundleIdentifier() + ).toLowerCase(); return `${scheme}://${domain}/${Platform.OS}/${bundleIdentifier}/callback`; } } From c006eb837cd3beae6484ab43ddb80b91d24c48d9 Mon Sep 17 00:00:00 2001 From: Subhankar Maiti <35273200+subhankarmaiti@users.noreply.github.com> Date: Thu, 17 Apr 2025 22:54:41 +0530 Subject: [PATCH 02/50] Release v5.0.0 beta.0 (#1144) --- .eslintrc.js | 2 +- .github/actions/get-prerelease/action.yml | 2 +- .github/actions/release-create/action.yml | 2 +- .github/dependabot.yml | 10 ++-- .github/stale.yml | 2 +- .prettierrc.js | 2 +- .version | 2 +- .watchmanconfig | 2 +- CHANGELOG.md | 8 +++ docs/assets/hierarchy.js | 2 +- docs/classes/TimeoutError.html | 6 +- docs/classes/Types.Auth.html | 42 ++++++------- docs/classes/Types.BaseError.html | 6 +- docs/classes/Types.CredentialsManager.html | 18 +++--- docs/classes/Types.Users.html | 6 +- docs/classes/Types.WebAuth.html | 8 +-- docs/classes/default.html | 12 ++-- docs/enums/LocalAuthenticationLevel.html | 8 +-- docs/enums/LocalAuthenticationStrategy.html | 6 +- ...SafariViewControllerPresentationStyle.html | 22 +++---- docs/functions/Auth0Provider.html | 4 +- docs/functions/useAuth0.html | 2 +- docs/hierarchy.html | 2 +- docs/index.html | 2 +- docs/interfaces/AuthorizeUrlOptions.html | 8 +-- docs/interfaces/ClearSessionOptions.html | 6 +- docs/interfaces/ClearSessionParameters.html | 6 +- docs/interfaces/CreateUserOptions.html | 22 +++---- .../ExchangeNativeSocialOptions.html | 12 ++-- docs/interfaces/ExchangeOptions.html | 8 +-- docs/interfaces/GetUserOptions.html | 4 +- .../LocalAuthenticationOptions.html | 42 ++++++------- docs/interfaces/LoginWithEmailOptions.html | 10 ++-- docs/interfaces/LoginWithOOBOptions.html | 8 +-- docs/interfaces/LoginWithOTPOptions.html | 8 +-- .../LoginWithRecoveryCodeOptions.html | 6 +- docs/interfaces/LoginWithSMSOptions.html | 10 ++-- docs/interfaces/LogoutUrlOptions.html | 8 +-- .../MultifactorChallengeOptions.html | 8 +-- docs/interfaces/PasswordRealmOptions.html | 12 ++-- .../PasswordlessWithEmailOptions.html | 8 +-- .../PasswordlessWithSMSOptions.html | 8 +-- docs/interfaces/PatchUserOptions.html | 6 +- docs/interfaces/RefreshTokenOptions.html | 6 +- docs/interfaces/ResetPasswordOptions.html | 6 +- docs/interfaces/RevokeOptions.html | 4 +- .../Types.Auth0ContextInterface.html | 44 +++++++------- docs/interfaces/Types.AuthState.html | 10 ++-- docs/interfaces/UserInfoOptions.html | 4 +- docs/interfaces/WebAuthorizeOptions.html | 12 ++-- docs/interfaces/WebAuthorizeParameters.html | 22 +++---- docs/types/Credentials.html | 14 ++--- .../MultifactorChallengeOOBResponse.html | 2 +- ...factorChallengeOOBWithBindingResponse.html | 2 +- .../MultifactorChallengeOTPResponse.html | 4 +- docs/types/MultifactorChallengeResponse.html | 2 +- docs/types/Types.Auth0Response.html | 12 ++-- docs/types/Types.Telemetry.html | 8 +-- docs/types/User.html | 42 ++++++------- example/.watchmanconfig | 2 +- jest.environment.js | 2 +- package-lock.json | 4 +- package.json | 2 +- src/management/__tests__/users.spec.js | 48 +++++++-------- src/networking/__tests__/index.spec.js | 36 +++++------ src/networking/__tests__/telemetry.spec.js | 2 +- .../__tests__/timestampConversion.spec.js | 59 ++++++++++--------- src/utils/__tests__/userConversion.spec.js | 2 +- src/utils/__tests__/whitelist.spec.js | 38 ++++++------ src/utils/camel.ts | 2 +- src/utils/fetchWithTimeout.ts | 4 +- src/utils/timestampConversion.ts | 10 ++-- src/utils/whitelist.ts | 12 ++-- src/webauth/index.ts | 4 +- tsconfig.build.json | 9 ++- 75 files changed, 412 insertions(+), 396 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 0421db75..40c6dcd0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,4 @@ module.exports = { root: true, extends: '@react-native-community', -}; \ No newline at end of file +}; diff --git a/.github/actions/get-prerelease/action.yml b/.github/actions/get-prerelease/action.yml index 131f93d1..ce7acdc3 100644 --- a/.github/actions/get-prerelease/action.yml +++ b/.github/actions/get-prerelease/action.yml @@ -27,4 +27,4 @@ runs: echo "PRERELEASE=false" >> $GITHUB_OUTPUT fi env: - VERSION: ${{ inputs.version }} \ No newline at end of file + VERSION: ${{ inputs.version }} diff --git a/.github/actions/release-create/action.yml b/.github/actions/release-create/action.yml index a0db443d..6a2bf804 100644 --- a/.github/actions/release-create/action.yml +++ b/.github/actions/release-create/action.yml @@ -44,4 +44,4 @@ runs: fail_on_unmatched_files: ${{ inputs.fail_on_unmatched_files }} files: ${{ inputs.files }} env: - GITHUB_TOKEN: ${{ inputs.token }} \ No newline at end of file + GITHUB_TOKEN: ${{ inputs.token }} diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 94fc8b3d..b5f45518 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,10 +5,10 @@ updates: directory: / schedule: interval: daily - - package-ecosystem: "npm" - directory: "/" + - package-ecosystem: 'npm' + directory: '/' schedule: - interval: "daily" + interval: 'daily' ignore: - - dependency-name: "*" - update-types: ["version-update:semver-major"] + - dependency-name: '*' + update-types: ['version-update:semver-major'] diff --git a/.github/stale.yml b/.github/stale.yml index b2e13fc7..3cc35f17 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -17,4 +17,4 @@ staleLabel: closed:stale # Comment to post when marking as stale. Set to `false` to disable markComment: > - This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️ \ No newline at end of file + This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. If you have not received a response for our team (apologies for the delay) and this is still a blocker, please reply with additional information or just a ping. Thank you for your contribution! 🙇‍♂️ diff --git a/.prettierrc.js b/.prettierrc.js index 6e29914c..5c4de1a4 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -3,4 +3,4 @@ module.exports = { jsxBracketSameLine: true, singleQuote: true, trailingComma: 'all', -}; \ No newline at end of file +}; diff --git a/.version b/.version index 7422eeba..a0d1420f 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v4.5.0 \ No newline at end of file +v5.0.0-beta.0 \ No newline at end of file diff --git a/.watchmanconfig b/.watchmanconfig index 9e26dfee..0967ef42 100644 --- a/.watchmanconfig +++ b/.watchmanconfig @@ -1 +1 @@ -{} \ No newline at end of file +{} diff --git a/CHANGELOG.md b/CHANGELOG.md index db95e81c..86aa898d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +## [v5.0.0-beta.0](https://github.com/auth0/react-native-auth0/tree/v5.0.0-beta.0) (2025-04-17) + +[Full Changelog](https://github.com/auth0/react-native-auth0/compare/v4.5.0...v5.0.0-beta.0) + +**⚠️ BREAKING CHANGES** + +- BREAKING CHANGE: feat: Support Turbo Modules [\#1101](https://github.com/auth0/react-native-auth0/pull/1101) ([guabu](https://github.com/guabu)) + ## [v4.5.0](https://github.com/auth0/react-native-auth0/tree/v4.5.0) (2025-04-17) [Full Changelog](https://github.com/auth0/react-native-auth0/compare/v4.4.0...v4.5.0) diff --git a/docs/assets/hierarchy.js b/docs/assets/hierarchy.js index 9b539efa..4b04d197 100644 --- a/docs/assets/hierarchy.js +++ b/docs/assets/hierarchy.js @@ -1,2 +1,2 @@ window.hierarchyData = - 'eJyNj8FqwzAQRP9lzkrqCMs2uiWlh57bW/BB2BssIktFWkNL8L8XN9i4pSE5LezO8N5eEEPgBH3MKyVkUdYCkU6OGrbBJ+gLZKam4U1P0Hi3PYWBX2IMEQJn61vonawEhuig0TiTEqWndW7bce8grjdocGo3U3FzXYwCsihXjINJdB/w9UFpu0TvMASazro2koc+ykzVE7TMV9D9wF32HDzTJ796pngyDS0CUhWzgJ2Ps8O/zZs+S336O6/UH4U3Nvw49if9AOr3+2Vej+P4Dc50qC0='; + 'eJyNj8tqwzAQRf/lrpXUEfID7ZLSRdftLngh7AkWkaUijaEl+N+LG2zc0pCsBmbu5Zy5IIbACfqoKiVkUdYCkU6OGrbBJ+gLZJZPw5ueoPFuewoDv8QYIgTO1rfQO1kJDNFBo3EmJUpP69y2495BXG/Q4NRupuLmuhgFZFGuGAeT6D7g64PSdoneYQg0nXVtJA99lFleT9BSraD7gbvsOXimT371TPFkGloEZF7MAnY+zg7/Nm/6LPXpb1X9VXhjw49jf9IPoH6/X6p6HMdvzAuoKw=='; diff --git a/docs/classes/TimeoutError.html b/docs/classes/TimeoutError.html index 99d9333b..2b92f44f 100644 --- a/docs/classes/TimeoutError.html +++ b/docs/classes/TimeoutError.html @@ -113,7 +113,7 @@

  • Defined in utils/fetchWithTimeout.ts:3
  • @@ -250,7 +250,7 @@

  • Defined in utils/fetchWithTimeout.ts:4
  • @@ -300,7 +300,7 @@

  • Defined in auth/index.ts:585
  • @@ -723,7 +723,7 @@

  • Defined in auth/index.ts:305
  • @@ -1042,7 +1042,7 @@

  • Defined in auth/index.ts:406
  • @@ -1147,7 +1147,7 @@

  • Defined in auth/index.ts:373
  • @@ -1257,7 +1257,7 @@

  • Defined in auth/index.ts:440
  • @@ -1353,7 +1353,7 @@

  • Defined in auth/index.ts:337
  • @@ -1450,7 +1450,7 @@

  • Defined in auth/index.ts:474
  • @@ -1648,7 +1648,7 @@

  • Defined in auth/index.ts:262
  • @@ -1737,7 +1737,7 @@

  • Defined in auth/index.ts:279
  • @@ -1851,7 +1851,7 @@

  • Defined in auth/index.ts:566
  • @@ -2126,7 +2126,7 @@

  • Defined in auth/index.ts:503
  • @@ -2210,7 +2210,7 @@

  • Defined in auth/index.ts:530
  • diff --git a/docs/classes/Types.BaseError.html b/docs/classes/Types.BaseError.html index 3af4ac11..3e3b9e21 100644 --- a/docs/classes/Types.BaseError.html +++ b/docs/classes/Types.BaseError.html @@ -120,7 +120,7 @@

  • Defined in utils/baseError.ts:1
  • @@ -271,7 +271,7 @@

  • Defined in utils/baseError.ts:3
  • @@ -315,7 +315,7 @@

    Class CredentialsManager

  • Defined in credentials-manager/index.ts:8
  • @@ -223,8 +223,8 @@

  • Defined in credentials-manager/index.ts:123credentials-manager/index.ts:121
  • @@ -395,8 +395,8 @@

  • Defined in credentials-manager/index.ts:70credentials-manager/index.ts:68
  • @@ -487,8 +487,8 @@

  • Defined in credentials-manager/index.ts:110credentials-manager/index.ts:108
  • @@ -568,8 +568,8 @@

  • Defined in credentials-manager/index.ts:31credentials-manager/index.ts:29
  • diff --git a/docs/classes/Types.Users.html b/docs/classes/Types.Users.html index 1350c768..ef5b7e1a 100644 --- a/docs/classes/Types.Users.html +++ b/docs/classes/Types.Users.html @@ -126,7 +126,7 @@

  • Defined in webauth/index.ts:79
  • @@ -515,7 +515,7 @@

    Class default

  • Defined in auth0.ts:12
  • @@ -434,7 +434,7 @@

  • Defined in auth0.ts:28
  • @@ -482,7 +482,7 @@

  • Defined in auth0.ts:55
  • diff --git a/docs/enums/LocalAuthenticationLevel.html b/docs/enums/LocalAuthenticationLevel.html index 8a3df491..720669c8 100644 --- a/docs/enums/LocalAuthenticationLevel.html +++ b/docs/enums/LocalAuthenticationLevel.html @@ -103,7 +103,7 @@

    Enumeration LocalAuthenticationLevel

  • Defined in credentials-manager/localAuthenticationLevel.ts:5
  • @@ -206,7 +206,7 @@

    Enumeration LocalAuthenticationStrategy

  • Defined in credentials-manager/localAuthenticationStrategy.ts:4
  • @@ -201,7 +201,7 @@

    Enumeration SafariViewControllerPresentationStyle

  • Defined in types.ts:601
  • @@ -272,7 +272,7 @@

  • Defined in hooks/use-auth0.ts:38
  • diff --git a/docs/hierarchy.html b/docs/hierarchy.html index 7c06f82b..210d5d59 100644 --- a/docs/hierarchy.html +++ b/docs/hierarchy.html @@ -81,7 +81,7 @@

    react-native-auth0

    Hierarchy Summary

    -
    +

    Hierarchy (View Summary
  • Defined in hooks/auth0-context.ts:143
  • @@ -287,7 +287,7 @@

    Interface UserInfoOptions

  • Defined in types.ts:512
  • @@ -189,7 +189,7 @@

    Interface WebAuthorizeOptions

  • Defined in types.ts:110
  • @@ -273,7 +273,7 @@

    Interface WebAuthorizeParameters

  • Defined in types.ts:56
  • @@ -347,7 +347,7 @@

    Indexable

  • Defined in types.ts:1
  • @@ -284,7 +284,7 @@

    Type Alias MultifactorChallengeOOBResponse

  • Defined in types.ts:583
  • diff --git a/docs/types/MultifactorChallengeOOBWithBindingResponse.html b/docs/types/MultifactorChallengeOOBWithBindingResponse.html index 7759e781..f5fa3b32 100644 --- a/docs/types/MultifactorChallengeOOBWithBindingResponse.html +++ b/docs/types/MultifactorChallengeOOBWithBindingResponse.html @@ -119,7 +119,7 @@

    Type Alias MultifactorChallengeOOBWithBindingResponse

  • Defined in types.ts:587
  • diff --git a/docs/types/MultifactorChallengeOTPResponse.html b/docs/types/MultifactorChallengeOTPResponse.html index 1ab08471..f06b9b24 100644 --- a/docs/types/MultifactorChallengeOTPResponse.html +++ b/docs/types/MultifactorChallengeOTPResponse.html @@ -112,7 +112,7 @@

    Type Alias MultifactorChallengeOTPResponse

  • Defined in types.ts:581
  • @@ -187,7 +187,7 @@

    Type Alias MultifactorChallengeResponse

  • Defined in types.ts:592
  • diff --git a/docs/types/Types.Auth0Response.html b/docs/types/Types.Auth0Response.html index f912079c..80f9fdde 100644 --- a/docs/types/Types.Auth0Response.html +++ b/docs/types/Types.Auth0Response.html @@ -148,7 +148,7 @@

    Type Parameters

  • Defined in networking/index.ts:135
  • @@ -259,7 +259,7 @@

    Type Alias Telemetry

  • Defined in networking/telemetry.ts:3
  • @@ -211,7 +211,7 @@

    Indexable

  • Defined in types.ts:29
  • @@ -490,7 +490,7 @@
  • Defined in types.ts:537types.ts:634
  • @@ -200,13 +211,13 @@

    Indexable

    Index
    @@ -250,6 +261,15 @@

    Properties

    given_name? + + headers?
  • Defined in types.ts:549types.ts:646
  • @@ -380,8 +400,8 @@
    +
    + +
    + headers?: + Record<string, + string> +
    +
    +

    (Optional) Custom headers to include in the request.

    +
    +
    + +
    +
    + +
    + headers?: + Record<string, + string> +
    +
    +

    (Optional) Custom headers to include in the request.

    +
    +
    + +
    +
    + +
    + headers?: + Record<string, + string> +
    +
    +

    (Optional) Custom headers to include in the request.

    +
    +
    + +
    +
    + +
    + headers?: + Record<string, + string> +
    +
    +

    (Optional) Custom headers to include in the request.

    +
    +
    + +
    +
    + +
    + headers?: + Record<string, + string> +
    +
    +

    (Optional) Custom headers to include in the request.

    +
    +
    + +
    +
    + +
    + headers?: + Record<string, + string> +
    +
    +

    (Optional) Custom headers to include in the request.

    +
    +
    + +
    +
    + +
    + headers?: + Record<string, + string> +
    +
    +

    (Optional) Custom headers to include in the request.

    +
    +
    + +
    +
    + +
    + headers?: + Record<string, + string> +
    +
    +

    (Optional) Custom headers to include in the request.

    +
    +
    + +
    +
    + +
    + headers?: + Record<string, + string> +
    +
    +

    (Optional) Custom headers to include in the request.

    +
    +
    + +
    +
    + +
    + headers?: + Record<string, + string> +
    +
    +

    (Optional) Custom headers to include in the request.

    +
    +
    + +
    +
    + +
    + headers?: + Record<string, + string> +
    +
    +

    (Optional) Custom headers to include in the request.

    +
    +
    +