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: