處理認證方式 - 使用者可以輸入認證物件或在節點設定窗格中直接設定,並選擇提供密碼或令牌

所以,我正在開發一個自訂節點,有非常多的配置選項。雖然不是特別複雜,只是比較繁忙。我一直在絞盡腦汁的一件事是如何處理認證 – 基本上在兩個不同的集合中給出四個不同的選項。

使用者首先得到的選擇是認證是否「硬編碼」為認證物件(基於 .credentials.ts 定義)或在節點的屬性中動態提供(在 .node.ts 檔案中定義),適用於單一管道可能在多站點設置中使用的情況(允許認證從安全來源動態傳遞;「從何處」超出了我的影響範圍,所以這似乎是最合理的方法)。

第二個選擇是使用者是直接提供 API 令牌還是提供密碼來取得令牌。如果使用者提供令牌,只需將其編碼並放入任何請求的 Auth 標頭中 – 沒有問題。但如果他們提供密碼,節點需要先進行「特殊」請求,使用自訂 Auth 標頭,其中包含從使用者名稱和密碼構建的 base64 編碼字串,並從該請求返回令牌。

在實現的途中,我意識到我必須手動處理許多這樣的情況,所以 .credentials.ts 檔案只是收集資料(如果使用者採用這個方式)-- 就像節點的輸入一樣。我定義了一個從單獨檔案匯出的 apiRequest() 函式,用於處理 API 請求本身,以及同一檔案中的 getApiToken() 函式(未匯出 – 它目前僅被 apiRequest() 使用)專門處理令牌的請求。

使用者可以為任一選項選擇其中之一。無論如何,輸入都是相同的:使用者名稱、站點 URL 和密碼或 API 令牌。我已經完成了所有沒有密碼選項的工作,但我今天開始了這項工作,並用動態認證選項使其正常運作,但這似乎破壞了從 .credentials.ts 型配置中提取認證的任何能力。

我已經將問題縮小到 .node.ts 檔案中的一段程式碼,它只是一系列條件式賦值。

首先,它宣告一個變數 – credentials – 並嘗試使用 getCredentials().credentials.ts 配置中提取它們。如果不存在,有一個錯誤捕捉,只是將 credentials 預設為 undefined。緊接著,宣告一個常數 – dynamicConnection – 它使用 getNodeParameter() 從節點的屬性輸入中取得輸入(每個屬性都是型別安全的,指定為 string)-- 所以如果那裡沒有任何東西,它們就是 null。接下來,建立一個 connection 物件,其中四個屬性中的每一個都根據 credentialsdynamicConnection 物件屬性的條件式檢查進行賦值。對於每一個,我們檢查 credentials.<prop> 物件是否包含字串;如果包含,它就被賦值,如果沒有,我們對 dynamicConnection 做同樣的事情。如果兩者都不包含字串值,它預設為 undefined。最後,一個 if() 條件檢查以確保我們有 (a) 使用者名稱、(b) 實例 URL 和 (c) 密碼或令牌之一;如果不滿足任何這些條件,它會拋出錯誤。該 if() 條件讀作「如果 NOT 使用者名稱 -OR- NOT 站點 URL -OR- ( NOT API 令牌 -AND- NOT 密碼):拋出錯誤。」

這適用於動態輸入認證,但如果我嘗試用已保存的認證執行此操作,它們會從該 if() 塊中返回錯誤,好像遺漏了什麼,即使我知道所有欄位都在那裡。關鍵是 – 在我將 password 屬性新增到 connection 之前,這運作得很好,所以如果我預期在實現密碼欄位之前就失敗了,我真的不認為這是賦值問題。

我覺得我遺漏了一些絕對愚蠢的東西,但我希望這裡有人比我自己對此有更多的見解。或者至少能確認我沒有瘋掉?請先謝謝了!

@robby.emslie 你沒有瘋狂,這是我的推測:新增密碼選項時加入了 getNodeParameter 呼叫(或一個隱藏欄位的 displayOptions auth-selector),而 getNodeParameter 在無法解析 prop 時會拋出「Could not get parameter」錯誤,這在 saved-cred 路徑上就是這種情況。如果那個 throw 落在你用來將認證設為 null 的同一個 try/catch 中,整個 saved-cred 物件就會被清除,你的 if() 就會觸發為缺失。給每個 getNodeParameter 加上一個預設值(this.getNodeParameter(‘password’, i, ‘’)),這樣它就不會拋出錯誤,並將 try 範圍限制在 getCredentials 之內。

另外,對於你手動實作的 getApiToken() 密碼轉換為權杖的部分,n8n 認證有一個內建的 preAuthentication hook,它會執行預請求、抓取權杖並快取它,然後 authenticate 會注入它。MetabaseApi 和 Auth0ManagementApi 認證的做法就是完全相同的 username/password 轉換為權杖,值得為 saved-cred 路由複製那個模式。

1個讚

@robby.emslie 這聞起來像是字段的指派方式和驗證方式之間不匹配的問題。

最快看出問題所在的方法 — 在 if() 之前記錄已解析的物件,在儲存的憑證路徑上:

console.log(JSON.stringify({ credentials, dynamicConnection, connection }, null, 2));

我會押注在兩件事上:

  1. 空字符串 vs undefined。透過 getCredentials(),未使用的字段(像是權杖路由上的密碼)通常以「」而不是 undefined 的形式回傳。「」通過「typeof === ‘string’」指派檢查,但在 if() 中是假值 — 所以驗證器拋出「missing」,即使該字段在技術上確實存在。讓兩者保持一致,例如:
    const val = (typeof x === ‘string’ && x.trim() !== ‘’) ? x : undefined;

  2. 那個最終 if() 的優先順序/分組。!user || !url || !token && !password 評估為 !user || !url || (!token && !password) — 這是你想要的。但如果實際代碼是 ... || !token || !password,則每當密碼為空時就會拋出,即使存在有效的權杖。這個單一 OR/AND 交換完全符合你的「在我添加密碼屬性後就開始出現問題」症狀 — 之前,條件中根本沒有 password 術語。

如果你貼上 .node.ts 區塊(四個指派 + if 語句),我可以精確定位問題。

1個讚

上面的兩個回覆涵蓋了可能的錯誤。我要補充一個防護措施:讓已儲存的認證資料和內聯輸入都解析為同一個規範化的連接物件,刪除空字符串、記錄認證模式,並且不記錄原始機密。然後只驗證該物件。這樣可以更輕鬆地進行除錯,而不會洩露令牌。

3個讚

這裡出了大問題——哈!我想我今天下午基本上得重新建置一遍。我某個地方搞砸了。不過我很好奇那個內建的 hook——我會看看 Metabase 和 Auth0Management 的範本。我原本實際上試過用那個,我覺得快成功了,但後來當我加入動態認證選項時,我就破壞了什麼東西。

不過這真的超有幫助。非常感謝你。我覺得問題是在 getCredentials() 某個地方發生的,但從這個角度來說,花時間去找出什麼地方出錯,還不如從我上次已知可行的配置重新建立我的 repo 來得容易。

謝了,朋友!

我想回到這個問題並向你致謝。你提出查看 Metabase 和 Auth0Management 的建議真是救命稻草。

我認為這解決了我遇到的問題,但我想從 dynamicCredentials 中移除密碼型身份驗證。我覺得那是個等待發生的安全漏洞。它已經可以運作了,但我打算把它註解掉,如果接收端的某個人想啟用它,那就由他們負責了。:slightly_smiling_face: 哈哈

無論如何,再次感謝你,@achamm

1個讚

@robby.emslie 很高興能幫上忙!歡迎將任何回覆標記為解決方案,祝你好運!