Firebase предлагает широкий спектр решений, которые помогут вам легко интегрировать такие функции, как аутентификация пользователей и запросы к базе данных, в ваше мобильное и веб-приложение. Хотя интеграция Cloud Firestore непосредственно в клиент вашего мобильного приложения может показаться беспроблемной для взаимодействия с базой данных, это может представлять угрозу безопасности, поскольку ваше приложение может быть манипулировано для получения несанкционированного доступа к вашей коммерческой тайне, которая находится внутри вашего приложения. Интеграция Cloud Firestore непосредственно в ваше мобильное приложение также может привести к тому, что вы потратите больше времени, если захотите расшириться до веб-приложения и нуждаетесь в том же сервисе. Исходя из этих причин, может быть хорошей идеей разместить ваши серверные службы в облаке.

К счастью, облачные функции позволяют это сделать. Вы можете развернуть серверные функции, такие как запросы к базе данных, тяжелые вычисления, и их можно запустить с помощью метода ответа на http-запрос.

Я создал простое приложение для Android и скрипт облачной функции, чтобы показать вам концепцию, стоящую за ним.

Во-первых, нам нужно установить Firebase CLI:

npm install -g firebase-tools

Войдите и аутентифицируйтесь через браузер с помощью команды:

firebase login

Инициализируйте проект и начните установку всех зависимостей:

firebase init functions

Во время инициализации вам будет предложено выбрать новый или существующий проект Firebase. Будет возможность выбора между Javascript или Typescript для вашего языка сценариев и возможность включения ESLint, который помогает искать потенциальные проблемы с вашим сценарием. Для этого примера я выбрал Javascript и отключил ESLint.

После завершения инициализации перейдите к функциям папки, и вы будете писать свой внутренний скрипт в index.js.

Чтобы развернуть скрипт, просто запустите:

firebase deploy --only functions

Из терминала вы можете увидеть логи функций в развертываемом скрипте:

Project Console: https://console.firebase.google.com/project/testfire-6b175/overview
DAVIDs-MBP:functions davidcheah$ firebase deploy --only functions
=== Deploying to 'testfire-6b175'...
i  deploying functions
i  functions: ensuring necessary APIs are enabled...
✔  functions: all necessary APIs are enabled
i  functions: preparing functions directory for uploading...
i  functions: packaged functions (42.53 KB) for uploading
✔  functions: functions folder uploaded successfully
i  functions: creating Node.js 6 function getAccountInfo(us-central1)...
i  functions: updating Node.js 6 function register(us-central1)...
i  functions: updating Node.js 6 function login(us-central1)...
i  functions: updating Node.js 6 function destroySession(us-central1)...
✔  functions[getAccountInfo(us-central1)]: Successful create operation.
Function URL (getAccountInfo): https://us-central1-testfire-6b175.cloudfunctions.net/getAccountInfo
✔  functions[destroySession(us-central1)]: Successful update operation.
✔  functions[login(us-central1)]: Successful update operation.
✔  functions[register(us-central1)]: Successful update operation.
✔  Deploy complete!
Project Console: https://console.firebase.google.com/project/testfire-6b175/overview
DAVIDs-MBP:functions davidcheah$ firebase deploy --only functions

В этом примере пользователю будет показан экран входа/регистрации. Сначала пользователь регистрируется в Firestore, а внутренний скрипт проверяет наличие любой существующей созданной учетной записи.

exports.register = functions.https.onRequest((req, res) => {
  if (req.method === 'PUT') {
    res.status(403).send('Forbidden!');
    return;
  }
  cors(req, res, () => {
    let name = req.query.name;
    //validations
    if (!name) {
      res.status(200).send("Please enter name.");
      return;
    }
    let email = req.query.email;
    if (!email) {
      res.status(200).send("Please enter email.");
      return;
    }
    let password = req.query.password;
    if (!password) {
      res.status(200).send("Please enter password.");
      return;
    }
    if (!validator.isLength(password, 3)) {
      res.status(200).send("Please enter valid password.");
      return;
    }
    if(!validator.isEmail(email)){
      res.status(200).send("Please enter valid email.");
      return;
    }
    
    //check if user already exists in firestore
    var userRef = admin.firestore().collection('users')
var userExists;
    userRef.where('email', '==', email).get()
    .then(snapshot => {
      userExists = snapshot.size;
      console.log(`user by email query size ${userExists}`);
      //send error if user exists
      if(userExists && userExists > 0){
        res.status(200).send("Account exists with same email Id.");
        return;
      }
      //add user to database
      admin.firestore().collection('users').add({
        name: name,
        email: email,
        password: password
      }).then(ref => {
        console.log('add user account', ref.id);
        res.status(200).send("User account created.");
        return; 
      });
})
    .catch(err => {
      console.log('error getting user by email', err);
      res.status(200).send("System error, please try again.");
    });
});
});

В случае успеха он направит пользователя на экран входа в систему, где пользователь вводит адрес электронной почты и пароль. Когда серверная часть проверяет совпадение, она отправляет ответ с уникальной строкой токена, которую приложение сохраняет в общей памяти настроек. Этот токен также хранится в Firestore.

exports.login = functions.https.onRequest((req, res) => {
  if (req.method === 'PUT') {
    res.status(403).send('Forbidden!');
    return;
  }
  cors(req, res, () => {
    let email = req.query.email;
    //validation
    if (!email) {
      res.status(200).send("Please enter email.");
      return;
    }
    if(!validator.isEmail(email)){
      res.status(200).send("Please enter valid email.");
      return;
    }
    let password = req.query.password;
    if (!password) {
      res.status(200).send("Please enter password.");
      return;
    }
    if (!validator.isLength(password, 3)) {
      res.status(200).send("Please enter valid password.");
      return;
    }
//get password from db and match it with input password
    var userRef = admin.firestore().collection('users')
userRef.where('email', '==', email).get()
    .then(snapshot => {
      if(snapshot.size > 1){
        res.status(200).send("Invalid account.");
        return;
      }
      snapshot.forEach(doc => {
        console.log(doc.id, '=>', doc.data().name);
        var userPass = doc.data().password;
//if password matches, generate token, save it in db and send it
        if(userPass && password == userPass){
          const tokgenGen = new TokenGenerator(256, TokenGenerator.BASE62);
          const tokenStr = tokgenGen.generate();
//save token in db to use for other client request's authentication verification
          var tokenData = { email: doc.data().email};
          admin.firestore().collection('tokens').doc(tokenStr).set(tokenData);
res.status(200).send("token:"+tokenStr );
        }else{
          res.status(200).send("Invalid email/password.");
        }
      });
    })
    .catch(err => {
      console.log('error getting user by email', err);
      res.status(200).send("System error, please try again.");
    });
});
});

После успешного входа в систему пользователь перенаправляется на экран информации об учетной записи, где будет отображаться баланс. В фоновом режиме приложение извлекает уникальную строку токена и отправляет серверному сценарию запрос на получение информации об учетной записи. Бэкэнд-скрипт получает токен и проверяет соответствие в Firestore. Как только это совпадение, информация об учетной записи пользователя будет выпущена.

exports.getAccountInfo = functions.https.onRequest((req, res) => {
    if (req.method === 'PUT') {
    res.status(403).send('Forbidden!');
    return;
    }
    cors(req, res, () => {
        let token = req.query.token;
        var tokenData = token;
    //validation
    if (!token) {
      res.status(200).send("Please login");
      return;
    } else {
            var tokenDoc = admin.firestore().collection('tokens').doc(token);
            tokenDoc.get()
            .then(doc => {
                //if token exists then send data otherwise error response
                if (!doc.exists) {
                    console.log('Invalid token');
                    res.status(200).send("Invalid token");
                } else {
                    console.log('valid token');
                    // TODO: Add code to get information from db
                    // Lets assume we get account balance from db
                    var accountBal = '$200';
                    res.status(200).send(accountBal);
                }
            });
        }
  });
});

После того, как пользователь завершит работу, можно выйти из системы, чтобы очистить любой токен в памяти общих настроек приложения, а также в Firestore.

exports.destroySession = functions.https.onRequest((req, res) => {
    if (req.method === 'PUT') {
        res.status(403).send('Forbidden!');
        return;
    }
    cors(req, res, () => {
        let token = req.query.token;
        var tokenData = token;
        //validation
        if (!token) {
            res.status(200).send("Please login");
            return;
        } else {
            // Delete token entry from db
            var tokenDoc = admin.firestore().collection('tokens').doc(token).delete();
            tokenDoc.delete();
            res.status(200).send("Delete success");  
        }
    });
});

Вот как это выглядит в приложении и в Firestore:

Исходный код Android и облачных функций доступен на моем github: