前言

其實起因只是一句午後笑話:「Hey!我們的會議連結怎麼還沒給?」。「蛤?什麼會議?」「我們的會議啊!」「我們有會議嗎?」「有啊!」「我要給連結?」「Uber Eats 會議啊!」「喔!靠北喔!」

所以我就想到可以做一個 Zoom Meeting 的短網址釣魚網站。當使用者以為點進去是 Zoom Meeting,但在按下 啟動會議 後會導向外送平台連結。

選擇 TechStack

原本是想要用 Nuxt.js + Firebase 的組合。但當時對 Firebase Admin SDK 不是很熟悉。文件內說要把一個 GCP 服務帳號的 JSON Key 放在程式碼內讀取。但我一直找不到 Nuxt.js 放這種金鑰的 best practice。再來就是就算我暫時放在某個資料夾內,試著使用 Firebase Admin SDK 讀取 Firestore 也沒法正確運作。

然後我就放棄了。

因為意志消沉,我在 YouTube 看了一下其他人做 SaaS 的 TechStack,看到這部提到 FKIT Stack。他是使用 SvelteKit + Firebase 的組合。Svelte 的那個 S logo 隱約有印象看過,但不太清楚是什麼東西。而 SvelteKit 我查了一下就是可以加上後端程式碼的 Svelte。那跟 Nuxt.js 蠻像的。雖然沒有實際看過 SvelteKit 是怎麼串接 Firebase,但我看 Google 搜尋上的文章蠻多的,我就 pnpm create svelte@latest sveltekit-meeting-alias-ninja 開幹(FUXK IT)。

產生短網址

我拿著 Zoom Meeting 的連結問 Chat GPT 那是用什麼演算法產生的。他告訴我是 base64 encode。我看著也蠻像的。我原本想將原網址 base64 encode,但我發現產生的網址比 Zoom 真實的網址還要長。所以我就先以 timestamp 用 base64 encode。是的,若是請求量大的話,這樣的短網址會有重複的問題。但目前這不是我關心的點,我想要趕快試著串接 Firebase 以便驗證整套流程會不會卡關。

串接 Firebase

我在 src/lib/firebase.ts 內設定了 Firebase 的初始化程式碼。這是我在 YouTube 上看到的。我也有在 Firebase 上建立一個專案且建立一個 Firestore。在 src/routes/index.svelte 內寫了一個簡單的程式碼,用來測試 Firebase 是否正確運作。在本機端執行 pnpm run dev 後,發現可以正確讀取到 Firestore 內的資料。這樣就代表 Firebase 的串接是成功的。而且可以透過 sveltekit 的 API 存取 Firestore。

購買 Domain

我在 Namecheap 上面買了 us06web-zoom.com。長得很像公司用的 us06web.zoom.us

殺人誅心

我在專案的 /j/* 頁面放了假的 Zoom Meeting 頁面。技術上不難,只需要在真實的 Zoom Meeting 頁面按下 Ctrl + S 就可以把 HTML 和 CSS 一併下載下來。然後把裡面的按鈕以及啟動會議連結替換一下,按下去就會導向外送平台的連結。這樣就可以做到短網址釣魚了。我也把 cookies 提示訊息的按鈕加上隱藏功能。因為直接下載 Zoom Meeting 頁面不會把 JavaScript 一起載下來,要自行把特定按鈕的動作補上。

我大可以跳過這個頁面,直接將連結導向外送平台頁面。但多了這個假的 Zoom Meeting 頁面讓使用者點按鈕,可以讓使用者被騙兩次,達到殺。人。誅。心的效果。

部署

在部署 SSR 應用程式至 Firebase 期間,在 workflow 檔案內需要設定

env:
 FIREBASE_CLI_EXPERIMENTS: webframeworks

當使用 FIREBASE_CLI_EXPERIMENTS: webframeworks 時,必須啟用 GCP 上的 Cloud Functions API、Cloud Build API、Artifact Registry API、Eventarc API。同時,Artifact Registry API 需要與帳單帳戶進行綁定。也就是說,這個時候就不是以 Firebase 的免費方案來部署了。

部署後發現 Firebase 上面的應用程式沒有正確的讀到設定檔,導致無法正確存取 Firebase Firestore,我原本以為是 dotenv 本身的問題。而且我在讀文件時發現有些錯誤,提 PR 還被 merge。

這個 repo 原本只有兩個 contributors,莫名多我一個,讓我對這文件產生不信任感。

靠著這份不信任感我又發了 issue。但就像 issue 內文所說的,之後查到是 pnpmnpm 在執行 install 指令時會有一點差異,所以我就改用 npm 來管理套件。

好吧,這點看起來就是我和 Node.js 不太熟。作者還提到可以改用 dotenvx 解決,但我是覺得作者只是在偷渡宣傳他的新專案(?)

後來改用 dotenvx 還是遇到線上環境沒辦法讀取到設定檔的問題。Google 查了一下也沒人講過整套 Sveltekit + Fireabse + .env 部署的方式,我看 YouTube 教學都把 Firebase 設定檔直接寫死在程式碼內,實在是很不應該,東西都教半套的。

也有人教可以把 .env 各個參數一個一個寫在 Repository secrets 內,就不使用 dotenv 了。這當然行得通,但以後有新的參數都要順帶在這邊新增一次,有夠麻煩。再來若是往後程式碼開源也不方便其他貢獻者共同參與專案,難道要在 README.md 教他們要在 Repository secrets 把參數一個一個設定進去?這樣對 GitHub 平台的相依性太高了。

過一陣子後我發現也許是 dotenvx 搭配 FirebaseExtended/action-hosting-deploy@v0 會遇到問題。因為如果直接在本機執行 firebase deploy,線上正式環境可以正常運作。表示 dotenvx 運作是正常的。這樣說的話,之前使用的 dotenv 本身也是正常的。是和 FirebaseExtended/action-hosting-deploy@v0 合併使用時發生了一些狀況。

後來,我試著不使用 GitHub Actions 進行部署,而是直接使用 GCP Cloud Run 內建的 trigger。這可以透過在專案中撰寫 Dockerfile 以 Docker 形式進行部署。然而,我也遇到了一些問題。忘記當時遇到什麼東西了,那時候深夜昏昏欲睡。

在進一步嘗試後,我突然在 dotenvx 文件內看到 dotenvx decrypt 指令,可以直接用 .env.keys 搭配 repo 內的 .env.vault 原地產生 .env 檔案。所以我在 workflow 內加上

- run: 'echo "DOTENV_KEY_PRODUCTION=$DOTENV_KEY" > .env.keys'
- run: dotenvx decrypt

package.json 內改回

{
  // ...
  "scripts": {
    "dev": "vite dev",
    "build": "vite build",
    "preview": "vite preview"
  },
  // ...
}

這樣就可以讓 GitHub Actions Runner 跑 build 的時候和本機端有 .env 時的狀況一樣。雖然這種做法不太標準,但目前似乎是最直接簡單的解決辦法。

最終,我重新使用 FirebaseExtended/action-hosting-deploy@v0,稍微調整了 GitHub Actions workflow 檔案,成功地完成了部署。

部署要注意的地方

在專案內的 firebase.json 檔案內,可以在 frameworksBackend 限制 Cloud Run 的資源使用量。這樣可以避免因為短時間內大量的請求而導致下個月的帳單跟你開玩笑

{
  "hosting": {
    "source": ".",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "frameworksBackend": {
      "region": "asia-east1",
      "minInstances": 0,
      "maxInstances": 1,
      "memory": "128MiB"
    }
  }
}

被誅心的,竟是我自己

做得差不多,部署上去後透過 Google Chrome 測試居然顯示危險網站,但 Safari 不會檢測到。

之後我使用 https://transparencyreport.google.com/safe-browsing/search?hl=zh-TW 看到的確被辨識為釣魚網站,我嘗試使用 https://gist.github.com/StevenACoffman/a5f6f682d94e38ed804182dc2693ed4b 混淆字元辨識,也盡量減少 Zoom 相關程式碼,但毫無作用。我就是個無頭蒼蠅瞎搗鼓,最後還是決定把 殺人誅心 這部分拿掉,改成直接導向外送連結。畢竟 世事如棋,留一手好下棋。

但還是沒有任何作用,不確定是不是被標記過後就會一直被記住。

2024-03-02 更新:經過幾個禮拜後,我發現透過直接跳轉的方式已經可以正常跳轉到外送平台了!

後記

我原本想說這個專案週末兩天就可以做完,但斷斷續續地做了兩個禮拜。還花 10 美金買一年的釣魚域名。

虧嗎?我開心就好。