OpenHarmony外設驅動使用 (五)
Fingerprint_auth
概述
功能簡介
指紋認證是端側設備不可或缺的功能,為設備提供用戶認證能力,可應用于設備解鎖、支付、應用登錄等身份認證場景。用戶注冊指紋后,指紋認證模塊就可為設備提供指紋認證的功能。指紋認證功能整體框架如圖1。
基于HDF(Hardware Driver Foundation)驅動框架開發的Fingerprint_auth驅動,能夠屏蔽硬件器件差異,為上層用戶認證框架和Fingerprint_auth服務提供穩定的指紋認證基礎能力接口,包括指紋認證執行器列表查詢、執行器信息查詢、指定指紋模板ID查詢模板信息、用戶認證框架和執行器間的指紋模板信息對賬、指紋的錄入、刪除、認證和識別等。
圖1 指紋認證功能整體框架
基本概念
用戶認證框架與各基礎認證服務組成的身份認證系統支持用戶認證憑據設置、刪除、認證等基礎功能。系統支持用戶身份認證,需要提供數據采集、處理、存儲及比對能力。
-
執行器
執行器是能夠提供以上能力的處理模塊,各基礎認證服務提供執行器能力,被身份認證框架調度完成各項基礎能力。
-
執行器安全等級
執行器提供能力時所在運行環境達到的安全級別。
-
執行器角色
-
全功能執行器:執行器可獨立處理一次憑據注冊和身份認證請求,即可提供用戶認證數據采集、處理、儲存及比對能力。
-
采集器:執行器提供用戶認證時的數據采集能力,需要和認證器配合完成用戶認證。
-
認證器:認證器提供用戶認證時數據處理能力,讀取存儲的憑據模板與當前認證信息完成比對。
-
-
執行器類型
同一種身份認證類型的不同認證方式會產生認證算法差異,設備器件差異也會導致算法差異,執行器根據支持的算法類型差異或對接的器件差異,會定義不同的執行器類型。
-
用戶認證框架公鑰 & 執行器公鑰
用戶身份認證處理需要保證用戶數據安全以及認證結果的準確性,用戶認證框架與基礎認證服務間的關鍵交互信息需要做數據完整性保護,各基礎認證服務將提供的執行器能力對接到用戶認證框架時,需要交換各自的公鑰,其中:
1)執行器通過用戶認證框架公鑰校驗調度指令的準確性。
2)執行器公鑰可被用戶認證框架用于校驗認證結果的準確性,同時用于執行器交互認證時的校驗交互信息的完整性。
-
認證憑據
在用戶設置認證憑據時認證服務會產生并存儲認證憑據,每個模板有一個ID,用于索引模板信息文件,在認證時讀取模板信息并用于與當次認證過程中產生的認證數據做對比,完成身份認證。
-
執行器對賬
用戶認證框架統一管理用戶身份和憑據ID的映射關系,執行器對接到用戶認證框架時,會讀取用戶身份認證框架內保存的該執行器的模板ID列表,執行器需要與自己維護的模板ID列表進行比對,并刪除冗余信息。
-
HAPs
HAPs(Harmony Ability Packages),廣義上指可以安裝在OpenHarmony上的應用包,本章節中僅代表Fingerprint_auth驅動的上層應用。
-
IDL接口
接口定義語言(Interface Definition Language)通過IDL編譯器編譯后,能夠生成與編程語言相關的文件:客戶端樁文件,服務器框架文件。本文主要是通過IDL接口生成的客戶端和服務端來實現Fingerprint_auth服務和驅動的通信,詳細使用方法可參考IDL簡介。
-
IPC通信
IPC(Inter Process Communication),進程間通信是指兩個進程的數據之間產生交互,詳細原理可參考IPC通信簡介。
-
HDI
HDI(Hardware Device Interface),硬件設備接口,位于基礎系統服務層和設備驅動層之間,是提供給硬件系統服務開發者使用的、統一的硬件設備功能抽象接口,其目的是為系統服務屏蔽底層硬件設備差異,具體可參考HDI規范。
運作機制
Fingerprint_auth驅動的主要工作是為上層用戶認證框架和Fingerprint_auth服務提供穩定的指紋認證基礎能力,保證設備上指紋認證功能可以正常運行。如圖2,Fingerprint_auth服務通過執行器列表接口獲取執行器信息后,向用戶認證框架注冊執行器。Fingerprint_auth服務通過執行器功能接口實現和Fingerprint_auth驅動的認證、識別、查詢等功能的信息交互。 開發者可基于HDF框架對不同芯片進行各自驅動的開發及HDI層接口的調用。
圖2 Fingerprint_auth服務和Fingerprint_auth驅動交互
約束與限制
要求設備上具有可信執行環境TEE(Trusted Execution Environment),指紋特征信息高強度加密保存在可信執行環境中。
開發指導
場景介紹
Fingerprint_auth驅動的主要工作是為上層用戶認證框架和Fingerprint_auth服務提供穩定的指紋認證基礎能力,保證設備上指紋認證功能可以正常運行。為實現上述場景的功能,開發者首先需要基于HDF驅動框架,完成Fingerprint_auth驅動開發,其次實現獲取執行器列表接口和認證、識別查詢等功能接口。
接口說明
注:以下接口列舉的為IDL接口描述生成的對應C++語言函數接口,接口聲明見idl文件(/drivers/interface/fingerprint_auth)。 在本文中,指紋憑據的錄入、認證、識別和刪除相關的HDI接口如表1所示,表2中的回調函數分別用于指紋執行器返回操作結果給框架和返回操作過程中的提示信息給上層應用。
表1 接口功能介紹
接口名稱 | 功能介紹 |
---|---|
GetExecutorList(std::vector<sptr<IAllInOneExecutor>>& allInOneExecutors) | 獲取V2_0版本執行器列表。 |
GetExecutorInfo(ExecutorInfo &executorInfo) | 獲取執行器信息,包括執行器類型、執行器角色、認證類型、安全等級、執行器公鑰等信息,用于向用戶認證框架注冊執行器。 |
OnRegisterFinish(const std::vector<uint64_t>& templateIdList, const std::vector<uint8_t>& frameworkPublicKey, const std::vector<uint8_t>& extraInfo) | 執行器注冊成功后,獲取用戶認證框架的公鑰信息;獲取用戶認證框架的模板列表用于對賬。 |
Enroll(uint64_t scheduleId, const std::vector<uint8_t>& extraInfo, const sptr<IExecutorCallback>& callbackObj) | 錄入指紋模板。 |
Authenticate(uint64_t scheduleId, const std::vector<uint64_t>& templateIdList, bool endAfterFirstFail, const std::vector<uint8_t>& extraInfo, const sptr<IExecutorCallback>& callbackObj) | 認證指紋模板(V2_0版本)。 |
Identify(uint64_t scheduleId, const std::vector<uint8_t>& extraInfo, const sptr<IExecutorCallback>& callbackObj) | 識別指紋模板。 |
Delete(const std::vector<uint64_t>& templateIdList) | 刪除指紋模板。 |
Cancel(uint64_t scheduleId) | 通過scheduleId取消指定錄入、認證、識別操作。 |
SendCommand(int32_t commandId, const std::vector<uint8_t>& extraInfo, const sptr<IExecutorCallback>& callbackObj) | 指紋認證服務向Fingerprint_auth驅動傳遞參數的通用接口。 |
GetProperty(const std::vector<uint64_t>& templateIdList, const std::vector<int32_t>& propertyTypes, Property& property) | 獲取執行器屬性信息。 |
SetCachedTemplates(const std::vector<uint64_t> &templateIdList) | 設置需緩存模板列表。 |
RegisterSaCommandCallback(const sptr<ISaCommandCallback> &callbackObj) | 注冊SA命令回調。 |
表2 回調函數介紹
接口名稱 | 功能介紹 |
---|---|
IExecutorCallback::OnResult(int32_t result, const std::vector<uint8_t>& extraInfo) | 返回操作的最終結果。 |
IExecutorCallback::OnTip(int32_t tip, const std::vector<uint8_t>& extraInfo) | 返回操作的過程交互信息。 |
ISaCommandCallback::OnSaCommands(const std::vector<SaCommand>& commands) | 發送命令列表。 |
開發步驟
以Hi3516DV300平臺為例,我們提供了Fingerprint_auth驅動DEMO實例,以下是目錄結構及各部分功能簡介。
// drivers/peripheral/fingerprint_auth
├── BUILD.gn # 編譯腳本
├── bundle.json # 組件描述文件
└── hdi_service # Fingerprint_auth驅動實現├── BUILD.gn # 編譯腳本├── include # 頭文件└── src # 源文件├── executor_impl.cpp # 認證、錄入等功能接口實現├── fingerprint_auth_interface_driver.cpp # Fingerprint_auth驅動入口└── fingerprint_auth_interface_service.cpp # 獲取執行器列表接口實現
下面結合DEMO實例介紹驅動開發的具體步驟。
-
Fingerprint_auth驅動是基于HDF驅動框架設計,所以開發者需要按照驅動Driver Entry程序,完成Fingerprint_auth驅動框架開發,主要由Bind、Init、Release、Dispatch函數接口實現。關鍵代碼如下,詳細代碼請參見fingerprint_auth_interface_driver.cpp文件。
// 通過自定義的HdfFingerprintAuthInterfaceHost對象包含ioService對象和真正的HDI Service實現IRemoteObject對象 struct HdfFingerprintAuthInterfaceHost {struct IDeviceIoService ioService;OHOS::sptr<OHOS::IRemoteObject> stub; };// 服務接口調用響應接口 static int32_t FingerprintAuthInterfaceDriverDispatch(struct HdfDeviceIoClient *client, int cmdId, struct HdfSBuf *data,struct HdfSBuf *reply) {auto *hdfFingerprintAuthInterfaceHost = CONTAINER_OF(client->device->service, struct HdfFingerprintAuthInterfaceHost, ioService);OHOS::MessageParcel *dataParcel = nullptr;OHOS::MessageParcel *replyParcel = nullptr;OHOS::MessageOption option;if (SbufToParcel(data, &dataParcel) != HDF_SUCCESS) {HDF_LOGE("%{public}s: invalid data sbuf object to dispatch", __func__);return HDF_ERR_INVALID_PARAM;}if (SbufToParcel(reply, &replyParcel) != HDF_SUCCESS) {HDF_LOGE("%{public}s: invalid reply sbuf object to dispatch", __func__);return HDF_ERR_INVALID_PARAM;}return hdfFingerprintAuthInterfaceHost->stub->SendRequest(cmdId, *dataParcel, *replyParcel, option); }// 初始化接口 static int HdfFingerprintAuthInterfaceDriverInit(struct HdfDeviceObject *deviceObject) {HDF_LOGI("%{public}s: driver init start", __func__);return HDF_SUCCESS; }// Fingerprint_auth驅動對外提供的服務綁定到HDF框架 static int HdfFingerprintAuthInterfaceDriverBind(struct HdfDeviceObject *deviceObject) {HDF_LOGI("%{public}s: driver bind start", __func__);auto *hdfFingerprintAuthInterfaceHost = new (std::nothrow) HdfFingerprintAuthInterfaceHost;if (hdfFingerprintAuthInterfaceHost == nullptr) {HDF_LOGE("%{public}s: failed to create create HdfFingerprintAuthInterfaceHost object", __func__);return HDF_FAILURE;}hdfFingerprintAuthInterfaceHost->ioService.Dispatch = FingerprintAuthInterfaceDriverDispatch;hdfFingerprintAuthInterfaceHost->ioService.Open = NULL;hdfFingerprintAuthInterfaceHost->ioService.Release = NULL;auto serviceImpl = OHOS::HDI::FingerprintAuth::V2_0::IFingerprintAuthInterface::Get(true);if (serviceImpl == nullptr) {HDF_LOGE("%{public}s: failed to get of implement service", __func__);delete hdfFingerprintAuthInterfaceHost;return HDF_FAILURE;}hdfFingerprintAuthInterfaceHost->stub = OHOS::HDI::ObjectCollector::GetInstance().GetOrNewObject(serviceImpl,OHOS::HDI::FingerprintAuth::V2_0::IFingerprintAuthInterface::GetDescriptor());if (hdfFingerprintAuthInterfaceHost->stub == nullptr) {HDF_LOGE("%{public}s: failed to get stub object", __func__);delete hdfFingerprintAuthInterfaceHost;return HDF_FAILURE;}deviceObject->service = &hdfFingerprintAuthInterfaceHost->ioService;return HDF_SUCCESS; }// 釋放Fingerprint_auth驅動中的資源 static void HdfFingerprintAuthInterfaceDriverRelease(struct HdfDeviceObject *deviceObject) {HDF_LOGI("%{public}s: driver release start", __func__);if (deviceObject->service == nullptr) {return;}auto *hdfFingerprintAuthInterfaceHost = CONTAINER_OF(deviceObject->service, struct HdfFingerprintAuthInterfaceHost, ioService);if (hdfFingerprintAuthInterfaceHost != nullptr) {delete hdfFingerprintAuthInterfaceHost;} }// 注冊Fingerprint_auth驅動入口數據結構體對象 struct HdfDriverEntry g_fingerprintAuthInterfaceDriverEntry = {.moduleVersion = 1,.moduleName = "drivers_peripheral_fingerprint_auth",.Bind = HdfFingerprintAuthInterfaceDriverBind,.Init = HdfFingerprintAuthInterfaceDriverInit,.Release = HdfFingerprintAuthInterfaceDriverRelease, };// 調用HDF_INIT將驅動入口注冊到HDF框架中。在加載驅動時HDF框架會先調用Bind函數,再調用Init函數加載該驅動。當Init調用異常時,HDF框架會調用Release釋放驅動資源并退出 HDF_INIT(g_fingerprintAuthInterfaceDriverEntry);
-
Fingerprint_auth驅動框架開發完成后,Fingerprint_auth驅動需要向Fingerprint_auth服務和統一身份認證注冊執行器,所以需要實現獲取執行器列表接口。關鍵代碼如下,詳細代碼請參見fingerprint_auth_interface_service.cpp文件。
// 執行器實現類 class AllInOneExecutorImpl : public IAllInOneExecutor { public:AllInOneExecutorImpl(struct ExecutorInfo executorInfo);virtual ~AllInOneExecutorImpl() {}private:struct ExecutorInfo executorInfo_; // 執行器信息 };static constexpr uint16_t SENSOR_ID = 123; // 執行器sensorID static constexpr uint32_t EXECUTOR_TYPE = 123; // 執行器類型 static constexpr size_t PUBLIC_KEY_LEN = 32; // 執行器32字節公鑰// 創建HDI服務對象 extern "C" IFingerprintAuthInterface *FingerprintAuthInterfaceImplGetInstance(void) {auto fingerprintAuthInterfaceService = new (std::nothrow) FingerprintAuthInterfaceService();if (fingerprintAuthInterfaceService == nullptr) {IAM_LOGE("fingerprintAuthInterfaceService is nullptr");return nullptr;}return fingerprintAuthInterfaceService; }// 獲取V2_0執行器列表實現 int32_t FingerprintAuthInterfaceService::GetExecutorList(std::vector<sptr<IAllInOneExecutor>> &executorList) {IAM_LOGI("interface mock start");for (auto executor : executorList_) {executorList.push_back(executor);}IAM_LOGI("interface mock success");return HDF_SUCCESS; }
-
步驟1、2完成后基本實現了Fingerprint_auth驅動和Fingerprint_auth服務對接。接下來需實現執行器每個功能接口,來完成指紋認證基礎能力。關鍵代碼如下,詳細代碼請參見all_in_one_executor_impl.cpp文件。
// 實現獲取執行器信息接口 int32_t AllInOneExecutorImpl::GetExecutorInfo(ExecutorInfo &executorInfo) {IAM_LOGI("interface mock start");executorInfo = executorInfo_;IAM_LOGI("get executor information success");return HDF_SUCCESS; }// 實現執行器注冊成功后,獲取用戶認證框架的公鑰信息、獲取用戶認證框架的模板列表接口。將公鑰信息保持,模板列表用于和本地的模板做對賬 int32_t AllInOneExecutorImpl::OnRegisterFinish(const std::vector<uint64_t> &templateIdList,const std::vector<uint8_t> &frameworkPublicKey, const std::vector<uint8_t> &extraInfo) {IAM_LOGI("interface mock start");static_cast<void>(templateIdList);static_cast<void>(extraInfo);static_cast<void>(frameworkPublicKey);IAM_LOGI("register finish");return HDF_SUCCESS; }// 實現指紋錄入接口 int32_t AllInOneExecutorImpl::Enroll(uint64_t scheduleId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj) {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}IAM_LOGI("enroll, result is %{public}d", ResultCode::OPERATION_NOT_SUPPORT);int32_t ret = callbackObj->OnResult(ResultCode::OPERATION_NOT_SUPPORT, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}return HDF_SUCCESS; }// 實現指紋認證接口 int32_t AllInOneExecutorService::Authenticate(uint64_t scheduleId, const std::vector<uint64_t> &templateIdList,const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj) {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);static_cast<void>(templateIdList);static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}IAM_LOGI("authenticate, result is %{public}d", ResultCode::NOT_ENROLLED);int32_t ret = callbackObj->OnResult(ResultCode::NOT_ENROLLED, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}return HDF_SUCCESS; }// 實現指紋識別接口 int32_t AllInOneExecutorService::Identify(uint64_t scheduleId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj) {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}IAM_LOGI("identify, result is %{public}d", ResultCode::OPERATION_NOT_SUPPORT);int32_t ret = callbackObj->OnResult(ResultCode::OPERATION_NOT_SUPPORT, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}return HDF_SUCCESS; }// 實現刪除指紋模板接口 int32_t AllInOneExecutorService::Delete(const std::vector<uint64_t> &templateIdList) {IAM_LOGI("interface mock start");static_cast<void>(templateIdList);IAM_LOGI("delete success");return HDF_SUCCESS; }// 實現通過scheduleId取消指定操作接口 int32_t AllInOneExecutorService::Cancel(uint64_t scheduleId) {IAM_LOGI("interface mock start");static_cast<void>(scheduleId);IAM_LOGI("cancel success");return HDF_SUCCESS; }// 實現指紋認證服務向Fingerprint_auth驅動傳遞參數的通用接口,當前需要實現凍結與解鎖模板命令 int32_t AllInOneExecutorService::SendCommand(int32_t commandId, const std::vector<uint8_t> &extraInfo, const sptr<IExecutorCallback> &callbackObj) {IAM_LOGI("interface mock start");static_cast<void>(extraInfo);if (callbackObj == nullptr) {IAM_LOGE("callbackObj is nullptr");return HDF_ERR_INVALID_PARAM;}int32_t ret;switch (commandId) {case DriverCommandId::LOCK_TEMPLATE:IAM_LOGI("lock template, result is %{public}d", ResultCode::SUCCESS);ret = callbackObj->OnResult(ResultCode::SUCCESS, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}break;case DriverCommandId::UNLOCK_TEMPLATE:IAM_LOGI("unlock template, result is %{public}d", ResultCode::SUCCESS);ret = callbackObj->OnResult(ResultCode::SUCCESS, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}break;case DriverCommandId::INIT_ALGORITHM:IAM_LOGI("init algorithm, result is %{public}d", ResultCode::SUCCESS);ret = callbackObj->OnResult(ResultCode::SUCCESS, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}break;default:IAM_LOGD("not support DriverCommandId : %{public}d", commandId);ret = callbackObj->OnResult(ResultCode::OPERATION_NOT_SUPPORT, {});if (ret != HDF_SUCCESS) {IAM_LOGE("callback result is %{public}d", ret);return HDF_FAILURE;}}return HDF_SUCCESS; }// 實現獲取執行器屬性接口 int32_t AllInOneExecutorService::GetProperty(const std::vector<uint64_t> &templateIdList, const std::vector<int32_t> &propertyTypes, Property &property) {IAM_LOGI("interface mock start");property = {};IAM_LOGI("get property success");return HDF_SUCCESS; }// 實現設置需緩存模板列表接口 int32_t AllInOneExecutorService::SetCachedTemplates(const std::vector<uint64_t> &templateIdList) {IAM_LOGI("interface mock start");IAM_LOGI("set cached templates success");return HDF_SUCCESS; }// 實現注冊SA命令回調接口 int32_t AllInOneExecutorService::RegisterSaCommandCallback(const sptr<ISaCommandCallback> &callbackObj) {IAM_LOGI("interface mock start");IAM_LOGI("register sa command callback success");return HDF_SUCCESS; }
-
用戶身份認證框架支持多driver,當增加driver或者修改driver信息,需要修改如下文件中serviceName2Config。
// base/user_iam/fingerprint_auth/services/src/fingerprint_auth_service.cpp void FingerprintAuthService::StartDriverManager() {IAM_LOGI("start");int32_t ret = UserAuth::IDriverManager::Start(HDI_NAME_2_CONFIG);if (ret != UserAuth::ResultCode::SUCCESS) {IAM_LOGE("start driver manager failed");} }
調測驗證
驅動開發完成后,開發者可以通過用戶認證API接口開發HAP應用,基于RK3568平臺驗證。認證和取消功能驗證的測試代碼如下:
1.發起認證并獲取認證結果的測試代碼如下:
// API version 10import type {BusinessError} from '@ohos.base';import userIAM_userAuth from '@ohos.userIAM.userAuth';// 設置認證參數const authParam: userIAM_userAuth.AuthParam = {challenge: new Uint8Array([49, 49, 49, 49, 49, 49]),authType: [userIAM_userAuth.UserAuthType.PIN, userIAM_userAuth.UserAuthType.FINGERPRINT],authTrustLevel: userIAM_userAuth.AuthTrustLevel.ATL3,};// 配置認證界面const widgetParam: userIAM_userAuth.WidgetParam = {title: '請進行身份認證',};try {// 獲取認證對象let userAuthInstance = userIAM_userAuth.getUserAuthInstance(authParam, widgetParam);console.info('get userAuth instance success');// 訂閱認證結果userAuthInstance.on('result', {onResult(result) {console.info(`userAuthInstance callback result: ${JSON.stringify(result)}`);// 可在認證結束或其他業務需要場景,取消訂閱認證結果userAuthInstance.off('result');}});console.info('auth on success');userAuthInstance.start();console.info('auth start success');} catch (error) {const err: BusinessError = error as BusinessError;console.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`);}
2.取消認證的測試代碼如下:
// API version 10import type {BusinessError} from '@ohos.base';import userIAM_userAuth from '@ohos.userIAM.userAuth';const authParam: userIAM_userAuth.AuthParam = {challenge: new Uint8Array([49, 49, 49, 49, 49, 49]),authType: [userIAM_userAuth.UserAuthType.PIN, userIAM_userAuth.UserAuthType.FINGERPRINT],authTrustLevel: userIAM_userAuth.AuthTrustLevel.ATL3,};const widgetParam: userIAM_userAuth.WidgetParam = {title: '請進行身份認證',};try {// 獲取認證對象let userAuthInstance = userIAM_userAuth.getUserAuthInstance(authParam, widgetParam);console.info('get userAuth instance success');// 開始認證userAuthInstance.start();console.info('auth start success');// 取消認證userAuthInstance.cancel();console.info('auth cancel success');} catch (error) {const err: BusinessError = error as BusinessError;console.error(`auth catch error. Code is ${err?.code}, message is ${err?.message}`);}