diff --git a/src/__tests__/index.test.tsx b/src/__tests__/index.test.tsx index 2b10054..383757c 100644 --- a/src/__tests__/index.test.tsx +++ b/src/__tests__/index.test.tsx @@ -4,6 +4,9 @@ import { getAttestation, isAttestationServiceAvailable, generateHardwareSignatureWithAssertion, + isPlayServicesAvailable, + prepareIntegrityToken, + requestIntegrityToken, } from '..'; jest.mock('react-native', () => ({ @@ -13,6 +16,9 @@ jest.mock('react-native', () => ({ generateHardwareKey: jest.fn(), getAttestation: jest.fn(), generateHardwareSignatureWithAssertion: jest.fn(), + requestIntegrityToken: jest.fn(), + prepareIntegrityToken: jest.fn(), + isPlayServicesAvailable: jest.fn(), }, }, Platform: { @@ -20,60 +26,105 @@ jest.mock('react-native', () => ({ }, })); +const mockPlatformiOS = () => + (Platform.select = jest.fn().mockImplementation((options) => options.ios)); + +const mockPlatformAndroid = () => + (Platform.select = jest + .fn() + .mockImplementation((options) => options.android)); + describe('Test integrity check function exposed by main package', () => { - it('should be called correctly on iOS', async () => { - Platform.OS = 'ios'; + describe('iOS functions', () => { + beforeAll(() => { + mockPlatformiOS(); + }); + + it('isAttestationServiceAvailable should be called correctly', async () => { + const spy = jest.spyOn( + NativeModules.IoReactNativeIntegrity, + 'isAttestationServiceAvailable' + ); + + await isAttestationServiceAvailable(); + + expect(spy).toHaveBeenCalled(); + }); - const spy = jest.spyOn( - NativeModules.IoReactNativeIntegrity, - 'isAttestationServiceAvailable' - ); + it('generateHardwareKey it should be called correctly', async () => { + const spy = jest.spyOn( + NativeModules.IoReactNativeIntegrity, + 'generateHardwareKey' + ); + await generateHardwareKey(); + expect(spy).toHaveBeenCalled(); + }); - await isAttestationServiceAvailable(); + it('getAttestation it should be called correctly', async () => { + const spy = jest.spyOn( + NativeModules.IoReactNativeIntegrity, + 'getAttestation' + ); + await getAttestation('challenge', 'hardwareKeyTag'); + expect(spy).toHaveBeenCalledWith('challenge', 'hardwareKeyTag'); + }); - expect(spy).toHaveBeenCalled(); + it('generateHardwareSignatureWithAssertion it should be called correctly', async () => { + const spy = jest.spyOn( + NativeModules.IoReactNativeIntegrity, + 'generateHardwareSignatureWithAssertion' + ); + await generateHardwareSignatureWithAssertion( + 'clientData', + 'hardwareKeyTag' + ); + expect(spy).toHaveBeenCalledWith('clientData', 'hardwareKeyTag'); + }); }); - it('should be called correctly on Android', async () => { - Platform.OS = 'android'; + describe('Android functions', () => { + beforeAll(() => { + mockPlatformAndroid(); + }); - const spy = jest.spyOn( - NativeModules.IoReactNativeIntegrity, - 'isAttestationServiceAvailable' - ); + it('isPlayServicesAvailable should be called correctly', async () => { + const spy = jest.spyOn( + NativeModules.IoReactNativeIntegrity, + 'isPlayServicesAvailable' + ); - await isAttestationServiceAvailable(); + await isPlayServicesAvailable(); - expect(spy).toHaveBeenCalled(); - }); + expect(spy).toHaveBeenCalled(); + }); - it('generateHardwareKey it should be called correctly', async () => { - const spy = jest.spyOn( - NativeModules.IoReactNativeIntegrity, - 'generateHardwareKey' - ); - await generateHardwareKey(); - expect(spy).toHaveBeenCalled(); - }); + it('prepareIntegrityToken should be called correctly', async () => { + const spy = jest.spyOn( + NativeModules.IoReactNativeIntegrity, + 'prepareIntegrityToken' + ); - it('getAttestation it should be called correctly', async () => { - const spy = jest.spyOn( - NativeModules.IoReactNativeIntegrity, - 'getAttestation' - ); - await getAttestation('challenge', 'hardwareKeyTag'); - expect(spy).toHaveBeenCalledWith('challenge', 'hardwareKeyTag'); - }); + await prepareIntegrityToken('cloudProjectNumber'); + + expect(spy).toHaveBeenCalledWith('cloudProjectNumber'); + }); + + it('requestIntegrityToken should be called correctly', async () => { + const spy = jest.spyOn( + NativeModules.IoReactNativeIntegrity, + 'requestIntegrityToken' + ); + await requestIntegrityToken('requestHash'); + expect(spy).toHaveBeenCalledWith('requestHash'); + }); - it('generateHardwareSignatureWithAssertion it should be called correctly', async () => { - const spy = jest.spyOn( - NativeModules.IoReactNativeIntegrity, - 'generateHardwareSignatureWithAssertion' - ); - await generateHardwareSignatureWithAssertion( - 'clientData', - 'hardwareKeyTag' - ); - expect(spy).toHaveBeenCalledWith('clientData', 'hardwareKeyTag'); + it('getAttestation it should be called correctly', async () => { + const spy = jest.spyOn( + NativeModules.IoReactNativeIntegrity, + 'getAttestation' + ); + await getAttestation('challenge', 'hardwareKeyTag'); + expect(spy).toHaveBeenCalledWith('challenge', 'hardwareKeyTag'); + }); }); }); diff --git a/src/index.tsx b/src/index.tsx index 0c3a433..4c2705e 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -27,9 +27,12 @@ type IntegrityErrorCodesIOS = | 'GENERATION_ASSERTION_FAILED' | 'DECODING_ASSERTION_FAILED'; +type IntegrityErrorCodesCommon = 'PLATFORM_NOT_SUPPORTED'; + export type IntegrityErrorCodes = | IntegrityErrorCodesIOS - | IntegrityErrorCodesAndroid; + | IntegrityErrorCodesAndroid + | IntegrityErrorCodesCommon; /** * Error type returned by a rejected promise. @@ -42,6 +45,14 @@ export type IntegrityError = { userInfo: Record; }; +/** + * Error when the platform is not supported. + */ +const IntegrityErrorUnsupportedError: IntegrityError = { + message: 'PLATFORM_NOT_SUPPORTED', + userInfo: {}, +}; + const LINKING_ERROR = `The package '@pagopa/io-react-native-integrity' doesn't seem to be linked. Make sure: \n\n` + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + @@ -60,6 +71,7 @@ const IoReactNativeIntegrity = NativeModules.IoReactNativeIntegrity ); /** + * iOS ONLY * This function checks if the attestation service is available on the device. * * If it is not possible to retrive the key, the promise is rejected providing an @@ -68,12 +80,14 @@ const IoReactNativeIntegrity = NativeModules.IoReactNativeIntegrity * @returns a promise that resolves to a boolean. */ export function isAttestationServiceAvailable(): Promise { - return Platform.OS === 'ios' - ? IoReactNativeIntegrity.isAttestationServiceAvailable() - : Promise.resolve(false); // TODO: implement for Android + return Platform.select({ + ios: () => IoReactNativeIntegrity.isAttestationServiceAvailable(), + default: () => Promise.reject(IntegrityErrorUnsupportedError), + })(); } /** + * iOS ONLY * This function generates a hardware key that can be used into the attestation process. * * If it is not possible to retrive the key, the promise is rejected providing an @@ -82,10 +96,14 @@ export function isAttestationServiceAvailable(): Promise { * @returns a promise that resolves to a string. */ export function generateHardwareKey(): Promise { - return IoReactNativeIntegrity.generateHardwareKey(); + return Platform.select({ + ios: () => IoReactNativeIntegrity.generateHardwareKey(), + default: () => Promise.reject(IntegrityErrorUnsupportedError), + })(); } /** + * iOS ONLY * This function generates an attestation for the given challenge and hardware key. * * If it is not possible to retrive the attestation, the promise is rejected providing an @@ -99,10 +117,16 @@ export function getAttestation( challenge: string, hardwareKeyTag: string ): Promise { - return IoReactNativeIntegrity.getAttestation(challenge, hardwareKeyTag); + return Platform.select({ + ios: () => IoReactNativeIntegrity.getAttestation(challenge, hardwareKeyTag), + android: () => + IoReactNativeIntegrity.getAttestation(challenge, hardwareKeyTag), + default: () => Promise.reject(IntegrityErrorUnsupportedError), + })(); } /** + * iOS ONLY * This function generates a signature for the given client data given an hardware key. * * If it is not possible to retrive the signature, the promise is rejected providing an @@ -116,10 +140,14 @@ export function generateHardwareSignatureWithAssertion( clientData: string, hardwareKeyTag: string ): Promise { - return IoReactNativeIntegrity.generateHardwareSignatureWithAssertion( - clientData, - hardwareKeyTag - ); + return Platform.select({ + ios: () => + IoReactNativeIntegrity.generateHardwareSignatureWithAssertion( + clientData, + hardwareKeyTag + ), + default: () => Promise.reject(IntegrityErrorUnsupportedError), + })(); } /** @@ -135,26 +163,16 @@ export function decodeAssertion(assertion: string): Promise { return IoReactNativeIntegrity.decodeAssertion(assertion); } -/** - * Checks whether the current platform is Android or not. - * @returns true if the current platform is Android, false otherwise. - */ -const isAndroid = () => Platform.OS === 'android'; - -/** - * Error message for functions available only on Android. - */ -const NOT_ANDROID_ERROR = 'This function is available only on Android'; - /** * ANDROID ONLY * Checks whether Google Play Services is available on the device or not. * @return a promise resolved to true if Google Play Services is available, to false otherwise. */ export function isPlayServicesAvailable(): Promise { - return isAndroid() - ? IoReactNativeIntegrity.isPlayServicesAvailable() - : Promise.resolve(false); + return Platform.select({ + android: () => IoReactNativeIntegrity.isPlayServicesAvailable(), + default: () => Promise.reject(IntegrityErrorUnsupportedError), + })(); } /** @@ -174,9 +192,11 @@ export function isPlayServicesAvailable(): Promise { export function prepareIntegrityToken( cloudProjectNumber: string ): Promise { - return isAndroid() - ? IoReactNativeIntegrity.prepareIntegrityToken(cloudProjectNumber) - : Promise.reject(NOT_ANDROID_ERROR); + return Platform.select({ + android: () => + IoReactNativeIntegrity.prepareIntegrityToken(cloudProjectNumber), + default: () => Promise.reject(IntegrityErrorUnsupportedError), + })(); } /** @@ -192,7 +212,8 @@ export function prepareIntegrityToken( * - The {@link prepareIntegrityToken} function hasn't been called previously. */ export function requestIntegrityToken(requestHash?: string): Promise { - return isAndroid() - ? IoReactNativeIntegrity.requestIntegrityToken(requestHash) - : Promise.reject(NOT_ANDROID_ERROR); + return Platform.select({ + android: () => IoReactNativeIntegrity.requestIntegrityToken(requestHash), + default: () => Promise.reject(IntegrityErrorUnsupportedError), + })(); }