iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 29
0
Modern Web

從0.5開始的JavaScript系列 第 29

Day29 前端福音(4/4): Firebase-帳號系統&資料讀寫規則

今日來到 Firebase 介紹的最後一天,此篇講完,鐵人賽也剩最後一天了,那就加把勁最後衝刺吧。

先來回顧一下

前三篇我們學會了:

  1. 新建專案
  2. 新增/讀取/修改/刪除資料
  3. 排序、限制語法

到這邊其實就可以實際用來開發,但是非常危險,

因為在沒有權限控管的情況下,你的資料就是大家的資料XD

所以我們要來學學怎麼處理這事!

本篇會分為兩個部分,第一部分會介紹 Firebase 使用者帳號建立與常見操作、第二部分會介紹資料讀寫規則常見的寫法。

本文開始


使用者帳號

只要專案需要使用者登入,大概就得寫很多程式碼

後端得處理

  • 使用者帳號新增
  • 密碼安全
  • 資料修改
  • 權限控制
  • token
  • 支援各大平台登入(Google、FB、Github...)
  • more...

如果非常巧地,您身兼多職,既是前端也是後端,那...這是個甜蜜的負擔啊...

還好 Firebase 有提供這方面的服務

只需要到主控台,然後點選 Authentication 就能看到。

「不需伺服器端程式碼,也能輕鬆驗證及管理不同供應商的使用者」

這句簡短的話充分說明了這個服務能替你解決什麼問題XD

點選「登入方式」即可看到所有能夠使用的登入方式

但今日篇幅有限,我們只會講如何使用電子郵件Firebasee 中實作登入。

Let's go!

電子郵件

首先我們需要一個註冊頁面,但是自己刻的可能不是很美觀,(咦...只有我嗎

那就找一個 login 的 template 來套用吧。

myLogin,這是使用 Bootstrap4 寫的,程式碼也乾淨又不失實用,喜歡的可以幫作者點個 Star。

不喜歡上面這個又不想自己刻的人,也可以考慮使用 Firebase Authentication 專門的 UI 庫

弄好之後記得引入 Firebase 的 CDN 還有專案的設定。

接下來的程式碼或有不清楚的可以直接查看官方文件

也有簡體版本

註冊

註冊的方法為:

firebase.auth().createUserWithEmailAndPassword(email, password)
.then(() => {
    ...
})
.catch((error) => {
    console.log(error.message);
});

很簡單,只需帶上 emailpassword 就好,然後可以使用 thencatch 來處理後續流程。

Step1 開通

但是在開始之前,需要先到控制台開通電子郵件登入的功能:

  1. 點選鉛筆圖示

  2. 啟用

  3. 儲存後就能夠看到電子郵件登入功能已經開通

既然是電子郵件,那就會有信箱驗證或是忘記密碼功能,這些 Firebase 也有提供,而且信件的內容也可以客製,有興趣的可以到「範本」的頁面玩玩看。

Step2

開通好後來到 register.html,然後把 JS 打好

接著就可以試著註冊囉!

註冊成功後,就能到使用者頁面看到剛剛新增的帳號

也不一定每次都要用 JS 建立,管理員可以直接在網頁上新增

But

在註冊時,記得一定要有 catch 來處理錯誤,因為 Firebase 會檢查 Email 是否符合格式、或是密碼長度是否足夠...等等。

像是這樣

又或者 Email 格式錯誤

登入

有了註冊功能後,接著需要登入的功能:

firebase.auth().signInWithEmailAndPassword(email, password)
.then(() => {
    ...
})
.catch((error) => {
  console.log(error.message);
});

但是怎麼知道有沒有登入成功呢?

可以使用這個方法來查看:

var user = firebase.auth().currentUser;

if (user) {
  // User is signed in.
} else {
  // No user is signed in.
}

如果有登入狀態的話,會回傳一個包含該使用者資料的物件,沒有則回傳 null

那就直接來試試看吧

登入

登入成功後,印出 firebase.auth().currentUser

裡面很多東西可能也不知道是什麼,不過有一些像是 EmaildisplayNameEmail 有沒有驗證uid 等等也可以在裡面看到。

但是 firebase.auth().currentUser 只會取得當前的登入狀態,也就是如果登出後,它也不會改變。

如果想要即時收到使用者當前的登入狀態,可以使用這個方法:

它會在登入狀態變動時執行 callback function,有點像是 Realtime databaseon 功能。

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    // User is signed in.
  } else {
    // No user is signed in.
  }
});

更新資料

上一步驟中,我們可以使用 firebase.auth().currentUser 來查看使用者資料,當中比較有用的像是 displayName 或是 photoURL,這些也可使日後更改。

實務上可以放到個人資料的頁面中,提供使用者修改自己的姓名等等,
使用的方法如下:

var user = firebase.auth().currentUser;

user.updateProfile({
    displayName: "new_hbdoy",
    photoURL: "https://hbdoy.tw/profile.jpg"
})
.then(() => {
    ...
})
.catch((error) => {
    ...
});

更多操作

這邊只列舉部分,還有剩下幾個功能可以使用

  • 驗證信箱
  • 寄送重設密碼信
  • 更改 Email
  • 刪除帳戶

有需要的可以直接查看官方文件

想要使用 Email 以外的方式登入,一樣可以查看官方文件,基本上 GoogleFB 的登入方法都大同小異。

讀寫權限

接著進入最後的部分,也就是資料庫存取的限制,總不可能讓讀寫都一直是 true

Firebase 也會一直提醒你這是不安全的規則

權限規則

主要使用這三個:

  • read: 限制讀取的權限
  • write 限制寫入的權限
  • validate: 資料驗證(ex: 要寫入的成績是不是在 0~100 之間)

範例一樣舉昨天的成績

像是這樣就是禁止中文成績的讀寫:

{
  "rules": {
    "chinese": {
      ".read": false,
      ".write": false
    }
  }
}

But

但是要注意,父層的規則會覆蓋過子層,

這個意思就是,假使 Bob 的讀取權限為 true,但是其父層 chinesefalse,所以還是沒有辦法讀取 Bob 的成績。

{
  "rules": {
    "chinese": {
      ".read": false,
      "Bob": {
        ".read": true,
      }
    }
  }
}

記得在設計權限規則的時候,要注意是否會有權限覆蓋的問題。

小工具

接下來測試讀寫規則時,不用辛苦地來回網頁與 JS,可以使用規則頁面的模擬工具

參數

再來是可以使用的參數

身分驗證

  • auth: 登入狀態

EX: 我們可以把規則改成只有登入的人才可以對資料進行讀寫

{
  "rules": {
    ".read": "auth != null",
    ".write": "auth != null"
  }
}

指定路徑

  • root: 資料庫頂層
  • child: 可以指定路徑
  • exists(): 指定路徑之資料是否存在

EX: chinese 成績中,是否存在 Tony 這個人

root.child("chinese/Tony").exists()

資料

  • newData: 使用者 set/push/update 時帶上的資料。

EX: 愈修改 Bob 成績,但是在寫入時先驗證 grade 欄位的值是否在 0 ~ 100 之間

{
  "rules": {
    "chinese": {
      "Bob":{
          ".write": "newData.child('grade').val() >= 0
          && newData.child('grade').val() <= 100
          && newData.child('grade').isNumber()"
      }
    }
  }
}

或是直接指定更深一層的路徑,這樣 newData 就會取得該路徑名稱的值,不用再加上 child('grade')

{
  "rules": {
    "chinese": {
      "Bob":{
        "grade":{
          ".write": "newData.val() >= 0
          && newData.val() <= 100
          && newData.isNumber()"
        }
      }
    }
  }
}

嘗試寫入小於 0 的數字,則會提示沒有權限寫入

其他還有 isStringlength 的方法。

  • data: 用法和 newData 差不多,只是指向的資料為資料庫。

EX: Bob 的國文成績

data.child('chinese/Bob/grade').val()

模糊參數

上面的範例中,我們限制了 Bob 的成績不能寫入 0~100 範圍外的數字,但是我們不確定每次寫入的人是誰,總不可能把資料庫所有的人名都寫上去,所以可以使用 $ 來指定不確定的傳入值。

EX: 使用 $student 來接收使用者寫入的人名,就算我們不知道那個人名是誰,但是限制了它 grade 欄位的值必須在 0~100 之間。

{
  "rules": {
    "chinese":{
      "$student": {
        ".write": "newData.child('grade').val() >= 0
          && newData.child('grade').val() <= 100
          && newData.child('grade').isNumber()"
      }
    }
  }
}

應用範例

綜合以上所學,我可以一開始註冊的時候,就順便建立一個 user 欄位來存放所有註冊的 Emailuid,然後設定他是否為 admin

接著限定只有登入且為 admin 才能夠寫入資料

{
  "rules": {
    ".read": "true",
    ".write": "auth != null
    && data.child('/user/'+ auth.uid + '/admin').val() == true"
  }
}

至此 Firebase 篇也告一個段落,我們學到如何建立帳號系統,也可以限制資料的存取規則。

今日的分享就到這,我們明天見/images/emoticon/emoticon51.gif


上一篇
Day28 前端福音(3/4): Firebase-讀取資料
下一篇
Day30 Final
系列文
從0.5開始的JavaScript30

1 則留言

0
existedinnettw
iT邦新手 5 級 ‧ 2020-10-19 08:21:37

那如果要限制chinese 裡的 entry 只有本人可以write,是要這樣寫嗎?
首先在chinese 裡的entry 都加上uid的column (類似RDBMS的foreign key)

chinese:{
    Alice:{
        uid:xxxx,
        grade:76
    }
    Bob:{
        uid:oooo,
        grade:32
    }
    ...
}

然後

{
  "rules": {
    "chinese":{
      "$student": {
        ".write": "$student.uid=auth.uid"
      }
    }
  }
}

我要留言

立即登入留言