Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(auth): Add Expo support for phone auth #6645

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
16 changes: 16 additions & 0 deletions docs/auth/phone-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ For reliable automated testing, you may want to disable both automatic and fallb

Ensure that all parts of step 1 and 2 from [the official firebase Android phone auth docs](https://firebase.google.com/docs/auth/android/phone-auth#enable-phone-number-sign-in-for-your-firebase-project) have been followed.

# Expo Setup

To use phone auth in an expo app, add the `@react-native-firebase/auth` config plug-in to the [`plugins`](https://docs.expo.io/versions/latest/config/app/#plugins) section of your `app.json`. This is in addition to the `@react-native-firebase/app` plugin.

```json
{
"expo": {
"plugins": ["@react-native-firebase/app", "@react-native-firebase/auth"]
}
}
```

The `@react-native-firebase/auth` config plugin is not required for all auth providers, but it is required to use phone auth. The plugin [will set up reCAPTCHA](https://firebase.google.com/docs/auth/ios/phone-auth#set-up-recaptcha-verification) verification for you on iOS.

The recommendation is to use a [custom development client](https://docs.expo.dev/clients/getting-started/). For more info on using Expo with React Native Firebase, see our [Expo docs](/#expo).

# Sign-in

The module provides a `signInWithPhoneNumber` method which accepts a phone number. Firebase sends an SMS message to the
Expand Down
1 change: 1 addition & 0 deletions packages/auth/app.plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./plugin/build');
11 changes: 10 additions & 1 deletion packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"scripts": {
"build": "genversion --semi lib/version.js",
"build:clean": "rimraf android/build && rimraf ios/build",
"prepare": "yarn run build"
"build:plugin": "rimraf plugin/build && tsc --build plugin",
"lint:plugin": "eslint plugin/src/*",
"prepare": "yarn run build && yarn run build:plugin"
},
"repository": {
"type": "git",
Expand All @@ -21,10 +23,17 @@
"firebase",
"auth"
],
"dependencies": {
"plist": "^3.0.5",
"@expo/config-plugins": "^5.0.1"
},
"peerDependencies": {
"@react-native-firebase/app": "16.3.1"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/plist": "^3.0.2"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Config Plugin iOS Tests adds url types to the Info.plist 1`] = `
{
"CFBundleURLTypes": [
{
"CFBundleURLSchemes": [
"com.googleusercontent.apps.SomeRandomClientIdString",
],
},
],
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CLIENT_ID</key>
<string>SomeRandomClientIdString.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.SomeRandomClientIdString</string>
<key>ANDROID_CLIENT_ID</key>
<string>SomeRandomAndroidClientIdString.apps.googleusercontent.com</string>
<key>API_KEY</key>
<string>SomeRandomApiKeyString</string>
<key>GCM_SENDER_ID</key>
<string>SomeRandomGcmSenderIdNumber</string>
<key>PLIST_VERSION</key>
<string>1</string>
<key>BUNDLE_ID</key>
<string>com.example.app</string>
<key>PROJECT_ID</key>
<string>example</string>
<key>STORAGE_BUCKET</key>
<string>example.appspot.com</string>
<key>IS_ADS_ENABLED</key>
<false></false>
<key>IS_ANALYTICS_ENABLED</key>
<false></false>
<key>IS_APPINVITE_ENABLED</key>
<true></true>
<key>IS_GCM_ENABLED</key>
<true></true>
<key>IS_SIGNIN_ENABLED</key>
<true></true>
<key>GOOGLE_APP_ID</key>
<string>1234:1234:ios:1234</string>
<key>DATABASE_URL</key>
<string>https://example.firebaseio.com</string>
</dict>
</plist>
75 changes: 75 additions & 0 deletions packages/auth/plugin/__tests__/iosPlugin.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import path from 'path';
import { beforeEach, describe, expect, it, jest } from '@jest/globals';
import { setUrlTypesForCaptcha } from '../src/ios/urlTypes';

describe('Config Plugin iOS Tests', () => {
beforeEach(function () {
jest.resetAllMocks();
});

it('throws if path to GoogleServer-Info.plist is not provided', async () => {
expect(() => {
setUrlTypesForCaptcha({
config: {
name: 'TestName',
slug: 'TestSlug',
modRequest: { projectRoot: path.join(__dirname, 'fixtures') } as any,
modResults: {},
modRawConfig: { name: 'TestName', slug: 'TestSlug' },
ios: {},
},
});
}).toThrow(
`[@react-native-firebase/auth] Your app.json file is missing ios.googleServicesFile. Please add this field.`,
);
});

it('throws if GoogleServer-Info.plist cannot be read', async () => {
const googleServiceFilePath = path.join(__dirname, 'fixtures', 'ThisFileDoesNotExist.plist');
expect(() => {
setUrlTypesForCaptcha({
config: {
name: 'TestName',
slug: 'TestSlug',
modRequest: { projectRoot: path.join(__dirname, 'fixtures') } as any,
modResults: {},
modRawConfig: { name: 'TestName', slug: 'TestSlug' },
ios: { googleServicesFile: 'ThisFileDoesNotExist.plist' },
},
});
}).toThrow(
`[@react-native-firebase/auth] GoogleService-Info.plist doesn't exist in ${googleServiceFilePath}. Place it there or configure the path in app.json`,
);
});

it('throws if GoogleServer-Info.plist has no reversed client id', async () => {
expect(() => {
setUrlTypesForCaptcha({
config: {
name: 'TestName',
slug: 'TestSlug',
modRequest: { projectRoot: path.join(__dirname, 'fixtures') } as any,
modResults: {},
modRawConfig: { name: 'TestName', slug: 'TestSlug' },
ios: { googleServicesFile: 'TestGoogleService-Info.incomplete.plist' },
},
});
}).toThrow(
'[@react-native-firebase/auth] Failed to parse your GoogleService-Info.plist. Are you sure it is a valid Info.Plist file with a REVERSE_CLIENT_ID field?',
);
});

it('adds url types to the Info.plist', async () => {
const result = setUrlTypesForCaptcha({
config: {
name: 'TestName',
slug: 'TestSlug',
modRequest: { projectRoot: path.join(__dirname, 'fixtures') } as any,
modResults: {},
modRawConfig: { name: 'TestName', slug: 'TestSlug' },
ios: { googleServicesFile: 'TestGoogleService-Info.plist' },
},
});
expect(result.modResults).toMatchSnapshot();
});
});
16 changes: 16 additions & 0 deletions packages/auth/plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ConfigPlugin, withPlugins, createRunOncePlugin } from '@expo/config-plugins';

import { withIosCaptchaUrlTypes } from './ios';

/**
* A config plugin for configuring `@react-native-firebase/auth`
*/
const withRnFirebaseAuth: ConfigPlugin = config => {
return withPlugins(config, [
// iOS
withIosCaptchaUrlTypes,
]);
};

const pak = require('@react-native-firebase/auth/package.json');
export default createRunOncePlugin(withRnFirebaseAuth, pak.name, pak.version);
3 changes: 3 additions & 0 deletions packages/auth/plugin/src/ios/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { withIosCaptchaUrlTypes } from './urlTypes';

export { withIosCaptchaUrlTypes };
90 changes: 90 additions & 0 deletions packages/auth/plugin/src/ios/urlTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
ConfigPlugin,
IOSConfig,
withInfoPlist,
ExportedConfigWithProps,
} from '@expo/config-plugins';
import fs from 'fs';
import path from 'path';
import plist from 'plist';

// does this for you: https://firebase.google.com/docs/auth/ios/phone-auth#enable-phone-number-sign-in-for-your-firebase-project
export const withIosCaptchaUrlTypes: ConfigPlugin = config => {
return withInfoPlist(config, config => {
return setUrlTypesForCaptcha({ config });
});
};

function getReversedClientId(googleServiceFilePath: string): string {
try {
const googleServicePlist = fs.readFileSync(googleServiceFilePath, 'utf8');

const googleServiceJson = plist.parse(googleServicePlist) as { REVERSED_CLIENT_ID: string };
const REVERSED_CLIENT_ID = googleServiceJson.REVERSED_CLIENT_ID;

if (!REVERSED_CLIENT_ID) {
throw new TypeError('REVERSED_CLIENT_ID missing');
}

return REVERSED_CLIENT_ID;
} catch {
throw new Error(
'[@react-native-firebase/auth] Failed to parse your GoogleService-Info.plist. Are you sure it is a valid Info.Plist file with a REVERSE_CLIENT_ID field?',
);
}
}

// add phone auth support by configuring recaptcha
// https://github.com/invertase/react-native-firebase/pull/6167
function addUriScheme(
config: ExportedConfigWithProps<IOSConfig.InfoPlist>,
reversedClientId: string,
): ExportedConfigWithProps<IOSConfig.InfoPlist> {
if (!config.modResults) {
config.modResults = {};
}

if (!config.modResults.CFBundleURLTypes) {
config.modResults.CFBundleURLTypes = [];
}

const hasReverseClientId = config.modResults.CFBundleURLTypes?.some(urlType =>
urlType.CFBundleURLSchemes.includes(reversedClientId),
);

if (!hasReverseClientId) {
config.modResults.CFBundleURLTypes.push({
CFBundleURLSchemes: [reversedClientId],
});
}

return config;
}

export function setUrlTypesForCaptcha({
config,
}: {
config: ExportedConfigWithProps<IOSConfig.InfoPlist>;
}) {
const googleServicesFileRelativePath = config.ios?.googleServicesFile;
if (!googleServicesFileRelativePath) {
throw new Error(
`[@react-native-firebase/auth] Your app.json file is missing ios.googleServicesFile. Please add this field.`,
);
}
const googleServiceFilePath = path.resolve(
config.modRequest.projectRoot,
googleServicesFileRelativePath,
);

if (!fs.existsSync(googleServiceFilePath)) {
throw new Error(
`[@react-native-firebase/auth] GoogleService-Info.plist doesn't exist in ${googleServiceFilePath}. Place it there or configure the path in app.json`,
);
}

const reversedClientId = getReversedClientId(googleServiceFilePath);
addUriScheme(config, reversedClientId);

return config;
}
9 changes: 9 additions & 0 deletions packages/auth/plugin/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "@tsconfig/node12/tsconfig.json",
"compilerOptions": {
"outDir": "build",
"rootDir": "src",
"declaration": true
},
"include": ["./src"]
}
Loading