使用 firebase 云功能验证 firebase 应用程序中是否存在电话号码

vin*_*t O 0 node.js firebase reactjs firebase-authentication google-cloud-functions

我是 Firebase(及其所有功能)领域的新手。我已经阅读了文档,并且能够正确使用 web sdk。我创建了一个文件,其中我所有当前的 firebase 代码都写在下面的 firebaseApi.js 中。另外,下面是我如何使用 registration.js 下的函数的示例(如果我做错了,请纠正),示例有效。我试图实施

 admin.auth().getUserByPhoneNumber(phoneNumber),
Run Code Online (Sandbox Code Playgroud)

我想用它来检查当前输入的电话号码是否已存在于应用程序中。但我已经阅读了 Admin SDKs 不能在客户端环境中使用,只能在 Firebase 应用程序开发人员拥有或管理的特权服务器环境中使用。我有点不知道如何解决这个问题。是否可以像我使用 firebaseApi 那样将 firebase 云功能连接到客户端?我已经清理了代码,只保留了相关部分

firebaseApi.js

        import firebase from 'firebase/app';
        import 'firebase/firestore';
        import 'firebase/auth';
        import 'firebase/database';
        import 'firebase/storage';

        const config = {config};

        firebase.initializeApp(config);

        class Firebase {
          register = ({ fullname, email, phone }) => {
            const user = Firebase.auth.currentUser.uid;
            const firestoreRef = Firebase.firestore.collection('Users').doc(user);

            const settings = {
              fullname,
              email,
              phone,
            };

            firestoreRef
              .set(settings);
          };

          static init() {
            Firebase.auth = firebase.auth();
            Firebase.firestore = firebase.firestore();
            Firebase.database = firebase.database();
            Firebase.storage = firebase.storage();
            Firebase.email = firebase.auth.EmailAuthProvider;
            Firebase.google = firebase.auth.GoogleAuthProvider;
            Firebase.phoneVerify = new firebase.auth.PhoneAuthProvider();
            Firebase.phone = firebase.auth.PhoneAuthProvider;
          }
        }

        Firebase.shared = new Firebase();
        export default Firebase;
Run Code Online (Sandbox Code Playgroud)

注册.js

          import Firebase from './firebaseApi';

          onCompleteReg() {
            const { fullname, email, email } = this.state;

            const settings = {
              fullname,
              email,
              email
            };

            Firebase.shared
              .registerSettings(settings)
              .then(() => {
                console.log('Successful');
              }).catch((e) => {
                console.log(e);
              })
          }
Run Code Online (Sandbox Code Playgroud)

sam*_*man 6

作为隐私和最佳实践的问题,除非当前用户是管理员,否则我不会公开检查任何给定电话号码是否被任何个人使用和/或与您的应用程序相关联的能力。

包裹在云函数中

由于 Admin SDK 只能在安全环境中使用,因此您只能通过某些 API 公开其功能。在这种情况下,自动处理用户身份验证和 CORS 是有益的,因此我将使用Callable Function。基于此类 API 的敏感性质,还建议对它的访问进行速率限制,这可以使用该firebase-functions-rate-limiter包轻松实现。在下面的代码中,我们将 API 调用限制为每个用户 2 次使用,所有用户每 15 秒使用 10 次,以防止滥用。

import * as admin from 'firebase-admin';
import * as functions from 'firebase-functions';
import { FirebaseFunctionsRateLimiter } from 'firebase-functions-rate-limiter';

admin.initializeApp();

const realtimeDb = admin.database();

const perUserLimiter = FirebaseFunctionsRateLimiter.withRealtimeDbBackend(
    {
        name: 'rate-limit-phone-check',
        maxCalls: 2,
        periodSeconds: 15,
    },
    realtimeDb
);
const globalLimiter = FirebaseFunctionsRateLimiter.withRealtimeDbBackend(
    {
        name: 'rate-limit-phone-check',
        maxCalls: 10,
        periodSeconds: 15,
    },
    realtimeDb
);

exports.phoneNumber = functions.https.onCall(async (data, context) => {
  // assert required params
  if (!data.phoneNumber) {
    throw new functions.https.HttpsError(
        'invalid-argument',
        'Value for "phoneNumber" is required.'
      );
  } else if (!context.auth || !context.auth.uid) {
    throw new functions.https.HttpsError(
        'failed-precondition',
        'The function must be called while authenticated.'
      );
  }

  // rate limiter
  const [userLimitExceeded, globalLimitExceeded] = await Promise.all(
      perUserLimiter.isQuotaExceededOrRecordUsage('u_' + context.auth.uid),
      globalLimiter.isQuotaExceededOrRecordUsage('global'));
  if (userLimitExceeded || globalLimitExceeded) {
    throw new functions.https.HttpsError(
        'resource-exhausted',
        'Call quota exceeded. Try again later',
      );
  }

  let userRecord = await admin.auth.getUserByPhoneNumber(phoneNumber);
  return userRecord.uid;
}
Run Code Online (Sandbox Code Playgroud)

要调用检查,您将在客户端上使用以下代码:

let checkPhoneNumber = firebase.functions().httpsCallable('phoneNumber');

checkPhoneNumber({phoneNumber: "61123456789"})
  .then(function (result) {
    let userId = result.data;
    // do something with userId
  })
  .catch(function (error) {
    console.error('Failed to check phone number: ', error)
  });
Run Code Online (Sandbox Code Playgroud)

登录尝试

与其让用户了解电话号码是否存在或专门存在于您的服务中,不如遵循电话号码身份验证流程并让他们证明他们拥有给定的电话号码。由于用户无法一次性验证多个号码,因此这是最安全的方法。

Firebase Phone Auth Reference 中,以下代码用于验证电话号码:

// 'recaptcha-container' is the ID of an element in the DOM.
var applicationVerifier = new firebase.auth.RecaptchaVerifier(
    'recaptcha-container');
var provider = new firebase.auth.PhoneAuthProvider();
provider.verifyPhoneNumber('+16505550101', applicationVerifier)
    .then(function(verificationId) {
      var verificationCode = window.prompt('Please enter the verification ' +
          'code that was sent to your mobile device.');
      return firebase.auth.PhoneAuthProvider.credential(verificationId,
          verificationCode);
    })
    .then(function(phoneCredential) {
      return firebase.auth().signInWithCredential(phoneCredential);
    });
Run Code Online (Sandbox Code Playgroud)

特权电话搜索

如果您希望具有适当特权的用户(无论他们是管理员还是管理角色)能够通过电话号码查询用户,您可以使用以下脚手架。在这些代码示例中,我将访问权限限制在isAdmin对他们的身份验证令牌有要求的人。

数据库结构:(有关更多信息,请参阅此答案

"phoneNumbers": {
  "c011234567890": { // with CC for US
    "userId1": true
  },
  "c611234567890": { // with CC for AU
    "userId3": true
  },
  ...
}
Run Code Online (Sandbox Code Playgroud)

数据库规则:

{
  "rules": {
    ...,
    "phoneNumbers": {
      "$phoneNumber": {
        "$userId": {
          ".write": "auth.uid === $userId && (!newData.exists() || root.child('users').child(auth.uid).child('phoneNumber').val() == ($phoneNumber).replace('c', ''))" // only this user can edit their own record and only if it is their phone number or they are deleting this record
        }
      },
      ".read": "auth != null && auth.token.isAdmin == true", // admins may read/write everything under /phoneNumbers
      ".write": "auth != null && auth.token.isAdmin == true"
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

辅助功能:

function doesPhoneNumberExist(phoneNumber) {
  return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).once('value')
    .then((snapshot) => snapshot.exists());
}
// usage: let exists = await doesPhoneNumberExist("611234567890")

function getUsersByPhoneNumber(phoneNumber) {
  return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).once('value')
    .then((snapshot) => snapshot.exists() ? Object.keys(snapshot.val()) : []);
}
// usage: let usersArray = await getUsersByPhoneNumber("611234567890") - normally only one user

function searchPhoneNumbersThatStartWith(str) {
  if (!str || str.length < 5) return Promise.reject(new Error('Search string is too short'));
  return firebase.database.ref("phoneNumbers").startAt("c" + str).endAt("c" + str + "\uf8ff").once('value')
    .then((snapshot) => {
      let phoneNumbers = [];
      snapshot.forEach((phoneEntrySnapshot) => phoneNumbers.push(phoneEntrySnapshot.key));
      return phoneNumbers;
    });
}
// usage: let matches = await searchPhoneNumbersThatStartWith("61455")

// best handled by Cloud Function not client
function linkPhoneNumberWithUser(phoneNumber, userId) {
  return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).child(userId).set(true);
}
// usage: linkPhoneNumberWithUser("611234567890", firebase.auth().currentUser.uid)

// best handled by Cloud Function not client
function unlinkPhoneNumberWithUser(phoneNumber, userId) {
  return firebase.database.ref("phoneNumbers").child("c" + phoneNumber).child(userId).remove();
}
// usage: unlinkPhoneNumberWithUser("611234567890", firebase.auth().currentUser.uid)
Run Code Online (Sandbox Code Playgroud)