@tatchi-xyz/sdk 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/core/EmailRecovery/index.js +25 -0
- package/dist/cjs/core/EmailRecovery/index.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/emailRecovery.js +135 -77
- package/dist/cjs/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/index.js +2 -1
- package/dist/cjs/core/TatchiPasskey/index.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/cjs/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/cjs/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/cjs/core/WalletIframe/client/router.js +1 -1
- package/dist/cjs/core/WalletIframe/client/router.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +3 -4
- package/dist/cjs/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/cjs/core/defaultConfigs.js +3 -7
- package/dist/cjs/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/core/nearCrypto.js +29 -5
- package/dist/cjs/core/nearCrypto.js.map +1 -1
- package/dist/cjs/core/rpcCalls.js +56 -26
- package/dist/cjs/core/rpcCalls.js.map +1 -1
- package/dist/cjs/core/types/emailRecovery.js +33 -0
- package/dist/cjs/core/types/emailRecovery.js.map +1 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{LinkedDevicesModal-CSSowiHP.css → LinkedDevicesModal-BRtht0XI.css} +1 -1
- package/dist/{esm/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map → cjs/react/components/AccountMenuButton/LinkedDevicesModal-BRtht0XI.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{ProfileDropdown-CEPMZ1gY.css → ProfileDropdown-BG_6hcim.css} +1 -1
- package/dist/{esm/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map → cjs/react/components/AccountMenuButton/ProfileDropdown-BG_6hcim.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css → Web3AuthProfileButton-k8_FAYFq.css} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css.map → Web3AuthProfileButton-k8_FAYFq.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css → TouchIcon-C-RcGfr5.css} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css.map → TouchIcon-C-RcGfr5.css.map} +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css → PasskeyAuthMenu-DKMiLeT9.css} +59 -4
- package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css.map → PasskeyAuthMenu-DKMiLeT9.css.map} +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/adapters/tatchi.js +1 -0
- package/dist/cjs/react/components/PasskeyAuthMenu/adapters/tatchi.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/client.js +30 -8
- package/dist/cjs/react/components/PasskeyAuthMenu/client.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/controller/useSDKEvents.js +22 -0
- package/dist/cjs/react/components/PasskeyAuthMenu/controller/useSDKEvents.js.map +1 -0
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js +17 -4
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +354 -154
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/cjs/react/components/{ShowQRCode-CCN4h6Uv.css → ShowQRCode-CB0UCQ_h.css} +1 -1
- package/dist/cjs/react/components/{ShowQRCode-CCN4h6Uv.css.map → ShowQRCode-CB0UCQ_h.css.map} +1 -1
- package/dist/cjs/react/context/useSDKFlowRuntime.js +183 -0
- package/dist/cjs/react/context/useSDKFlowRuntime.js.map +1 -0
- package/dist/cjs/react/context/useTatchiContextValue.js +24 -15
- package/dist/cjs/react/context/useTatchiContextValue.js.map +1 -1
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js +96 -0
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js.map +1 -0
- package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js +26 -0
- package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js +135 -77
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/index.js +2 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/index.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/WalletIframe/client/router.js +1 -1
- package/dist/cjs/react/sdk/src/core/WalletIframe/client/router.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +3 -4
- package/dist/cjs/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/defaultConfigs.js +3 -7
- package/dist/cjs/react/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/nearCrypto.js +29 -5
- package/dist/cjs/react/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/rpcCalls.js +56 -26
- package/dist/cjs/react/sdk/src/core/rpcCalls.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/types/emailRecovery.js +33 -0
- package/dist/cjs/react/sdk/src/core/types/emailRecovery.js.map +1 -0
- package/dist/cjs/server/email-recovery/emailParsers.js +2 -1
- package/dist/cjs/server/email-recovery/emailParsers.js.map +1 -1
- package/dist/cjs/server/email-recovery/index.js +6 -6
- package/dist/cjs/server/email-recovery/index.js.map +1 -1
- package/dist/cjs/server/email-recovery/rpcCalls.js +22 -3
- package/dist/cjs/server/email-recovery/rpcCalls.js.map +1 -1
- package/dist/cjs/server/router/cloudflare.js +8 -3
- package/dist/cjs/server/router/cloudflare.js.map +1 -1
- package/dist/cjs/server/router/express.js.map +1 -1
- package/dist/cjs/server/sdk/src/core/defaultConfigs.js +2 -4
- package/dist/cjs/server/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/server/sdk/src/core/nearCrypto.js +26 -7
- package/dist/cjs/server/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/esm/core/EmailRecovery/index.js +25 -1
- package/dist/esm/core/EmailRecovery/index.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/emailRecovery.js +136 -78
- package/dist/esm/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/index.js +2 -1
- package/dist/esm/core/TatchiPasskey/index.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/esm/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/esm/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/esm/core/WalletIframe/client/router.js +1 -1
- package/dist/esm/core/WalletIframe/client/router.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +2 -3
- package/dist/esm/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/esm/core/defaultConfigs.js +3 -7
- package/dist/esm/core/defaultConfigs.js.map +1 -1
- package/dist/esm/core/nearCrypto.js +24 -6
- package/dist/esm/core/nearCrypto.js.map +1 -1
- package/dist/esm/core/rpcCalls.js +56 -26
- package/dist/esm/core/rpcCalls.js.map +1 -1
- package/dist/esm/core/types/emailRecovery.js +26 -0
- package/dist/esm/core/types/emailRecovery.js.map +1 -0
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/components/AccountMenuButton/{LinkedDevicesModal-CSSowiHP.css → LinkedDevicesModal-BRtht0XI.css} +1 -1
- package/dist/{cjs/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map → esm/react/components/AccountMenuButton/LinkedDevicesModal-BRtht0XI.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{ProfileDropdown-CEPMZ1gY.css → ProfileDropdown-BG_6hcim.css} +1 -1
- package/dist/{cjs/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map → esm/react/components/AccountMenuButton/ProfileDropdown-BG_6hcim.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css → Web3AuthProfileButton-k8_FAYFq.css} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css.map → Web3AuthProfileButton-k8_FAYFq.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css → TouchIcon-C-RcGfr5.css} +1 -1
- package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css.map → TouchIcon-C-RcGfr5.css.map} +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css → PasskeyAuthMenu-DKMiLeT9.css} +59 -4
- package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css.map → PasskeyAuthMenu-DKMiLeT9.css.map} +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/adapters/tatchi.js +1 -0
- package/dist/esm/react/components/PasskeyAuthMenu/adapters/tatchi.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/client.js +30 -8
- package/dist/esm/react/components/PasskeyAuthMenu/client.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/controller/useSDKEvents.js +20 -0
- package/dist/esm/react/components/PasskeyAuthMenu/controller/useSDKEvents.js.map +1 -0
- package/dist/esm/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js +17 -4
- package/dist/esm/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +354 -154
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/esm/react/components/{ShowQRCode-CCN4h6Uv.css → ShowQRCode-CB0UCQ_h.css} +1 -1
- package/dist/esm/react/components/{ShowQRCode-CCN4h6Uv.css.map → ShowQRCode-CB0UCQ_h.css.map} +1 -1
- package/dist/esm/react/context/useSDKFlowRuntime.js +181 -0
- package/dist/esm/react/context/useSDKFlowRuntime.js.map +1 -0
- package/dist/esm/react/context/useTatchiContextValue.js +25 -16
- package/dist/esm/react/context/useTatchiContextValue.js.map +1 -1
- package/dist/esm/react/context/useTatchiWithSdkFlow.js +94 -0
- package/dist/esm/react/context/useTatchiWithSdkFlow.js.map +1 -0
- package/dist/esm/react/sdk/src/core/EmailRecovery/index.js +25 -1
- package/dist/esm/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js +136 -78
- package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/index.js +2 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/index.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/esm/react/sdk/src/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/esm/react/sdk/src/core/WalletIframe/client/router.js +1 -1
- package/dist/esm/react/sdk/src/core/WalletIframe/client/router.js.map +1 -1
- package/dist/esm/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +2 -3
- package/dist/esm/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/esm/react/sdk/src/core/defaultConfigs.js +3 -7
- package/dist/esm/react/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/react/sdk/src/core/nearCrypto.js +24 -6
- package/dist/esm/react/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/esm/react/sdk/src/core/rpcCalls.js +56 -26
- package/dist/esm/react/sdk/src/core/rpcCalls.js.map +1 -1
- package/dist/esm/react/sdk/src/core/types/emailRecovery.js +26 -0
- package/dist/esm/react/sdk/src/core/types/emailRecovery.js.map +1 -0
- package/dist/esm/react/styles/styles.css +58 -3
- package/dist/esm/sdk/{defaultConfigs-DpslkAQd.js → defaultConfigs-CfQDV-ya.js} +3 -7
- package/dist/esm/sdk/{getDeviceNumber-fXizNGQl.js → getDeviceNumber-BpernPnM.js} +4 -8
- package/dist/esm/sdk/getDeviceNumber-BpernPnM.js.map +1 -0
- package/dist/esm/sdk/offline-export-app.js +23 -6
- package/dist/esm/sdk/offline-export-app.js.map +1 -1
- package/dist/esm/sdk/{router-DuGYOd3G.js → router-BWtacLJg.js} +1 -1
- package/dist/esm/sdk/{rpcCalls-BQrJMTdg.js → rpcCalls-CYGJSCgm.js} +3 -3
- package/dist/esm/sdk/{rpcCalls-YVeUVMk2.js → rpcCalls-DZZSa-sk.js} +57 -27
- package/dist/esm/sdk/{transactions-bqaAwL4k.js → transactions-Cn9xTWlK.js} +2 -2
- package/dist/esm/sdk/{transactions-bqaAwL4k.js.map → transactions-Cn9xTWlK.js.map} +1 -1
- package/dist/esm/sdk/{transactions-BalIhtJ9.js → transactions-DfdwDQCn.js} +1 -1
- package/dist/esm/sdk/wallet-iframe-host.js +660 -590
- package/dist/esm/server/email-recovery/emailParsers.js +3 -1
- package/dist/esm/server/email-recovery/emailParsers.js.map +1 -1
- package/dist/esm/server/email-recovery/index.js +6 -6
- package/dist/esm/server/email-recovery/index.js.map +1 -1
- package/dist/esm/server/email-recovery/rpcCalls.js +22 -3
- package/dist/esm/server/email-recovery/rpcCalls.js.map +1 -1
- package/dist/esm/server/router/cloudflare.js +8 -3
- package/dist/esm/server/router/cloudflare.js.map +1 -1
- package/dist/esm/server/router/express.js.map +1 -1
- package/dist/esm/server/sdk/src/core/defaultConfigs.js +2 -4
- package/dist/esm/server/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/server/sdk/src/core/nearCrypto.js +26 -8
- package/dist/esm/server/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/esm/wasm_vrf_worker/pkg/wasm_vrf_worker_bg.wasm +0 -0
- package/dist/types/src/core/EmailRecovery/index.d.ts +8 -0
- package/dist/types/src/core/EmailRecovery/index.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts +8 -5
- package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/index.d.ts +1 -1
- package/dist/types/src/core/TatchiPasskey/index.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/scanDevice.d.ts.map +1 -1
- package/dist/types/src/core/WalletIframe/TatchiPasskeyIframe.d.ts +1 -1
- package/dist/types/src/core/WalletIframe/TatchiPasskeyIframe.d.ts.map +1 -1
- package/dist/types/src/core/WalletIframe/client/router.d.ts +1 -1
- package/dist/types/src/core/WalletIframe/client/router.d.ts.map +1 -1
- package/dist/types/src/core/WalletIframe/shared/messages.d.ts +1 -1
- package/dist/types/src/core/WalletIframe/shared/messages.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.d.ts +2 -1
- package/dist/types/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.d.ts.map +1 -1
- package/dist/types/src/core/defaultConfigs.d.ts.map +1 -1
- package/dist/types/src/core/nearCrypto.d.ts +14 -0
- package/dist/types/src/core/nearCrypto.d.ts.map +1 -1
- package/dist/types/src/core/rpcCalls.d.ts +11 -8
- package/dist/types/src/core/rpcCalls.d.ts.map +1 -1
- package/dist/types/src/core/types/emailRecovery.d.ts +10 -0
- package/dist/types/src/core/types/emailRecovery.d.ts.map +1 -0
- package/dist/types/src/core/types/index.d.ts +1 -0
- package/dist/types/src/core/types/index.d.ts.map +1 -1
- package/dist/types/src/core/types/tatchi.d.ts +0 -4
- package/dist/types/src/core/types/tatchi.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/adapters/tatchi.d.ts +2 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/adapters/tatchi.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/client.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/controller/useSDKEvents.d.ts +10 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/controller/useSDKEvents.d.ts.map +1 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/types.d.ts +8 -3
- package/dist/types/src/react/components/PasskeyAuthMenu/types.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/ContentSwitcher.d.ts +2 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/ContentSwitcher.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts.map +1 -1
- package/dist/types/src/react/context/useSDKFlowRuntime.d.ts +10 -0
- package/dist/types/src/react/context/useSDKFlowRuntime.d.ts.map +1 -0
- package/dist/types/src/react/context/useTatchiContextValue.d.ts.map +1 -1
- package/dist/types/src/react/context/useTatchiWithSdkFlow.d.ts +9 -0
- package/dist/types/src/react/context/useTatchiWithSdkFlow.d.ts.map +1 -0
- package/dist/types/src/react/types.d.ts +31 -0
- package/dist/types/src/react/types.d.ts.map +1 -1
- package/dist/types/src/server/email-recovery/emailParsers.d.ts.map +1 -1
- package/dist/types/src/server/email-recovery/index.d.ts +5 -6
- package/dist/types/src/server/email-recovery/index.d.ts.map +1 -1
- package/dist/types/src/server/email-recovery/rpcCalls.d.ts +1 -0
- package/dist/types/src/server/email-recovery/rpcCalls.d.ts.map +1 -1
- package/dist/types/src/server/router/cloudflare-adaptor.d.ts.map +1 -1
- package/dist/workers/wasm_vrf_worker_bg.wasm +0 -0
- package/package.json +1 -1
- package/dist/esm/sdk/getDeviceNumber-fXizNGQl.js.map +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ensureEd25519Prefix } from "../sdk/src/core/nearCrypto.js";
|
|
2
|
+
|
|
1
3
|
//#region src/server/email-recovery/emailParsers.ts
|
|
2
4
|
let EmailRecoveryModeHint = /* @__PURE__ */ function(EmailRecoveryModeHint$1) {
|
|
3
5
|
EmailRecoveryModeHint$1["ZkEmail"] = "zk-email";
|
|
@@ -81,7 +83,7 @@ function parseRecoverSubjectBindings(rawEmail) {
|
|
|
81
83
|
return {
|
|
82
84
|
requestId,
|
|
83
85
|
accountId,
|
|
84
|
-
newPublicKey
|
|
86
|
+
newPublicKey: ensureEd25519Prefix(newPublicKey)
|
|
85
87
|
};
|
|
86
88
|
}
|
|
87
89
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emailParsers.js","names":["headerLines: string[]"],"sources":["../../../../src/server/email-recovery/emailParsers.ts"],"sourcesContent":["import type { EmailRecoveryMode } from './types';\nimport { normalizeForwardableEmailPayload, parseAccountIdFromSubject } from './zkEmail';\n\nexport enum EmailRecoveryModeHint {\n ZkEmail = 'zk-email',\n TeeEncrypted = 'tee-encrypted',\n OnchainPublic = 'onchain-public',\n}\n\nexport function normalizeRecoveryMode(raw: string | undefined | null): EmailRecoveryMode | null {\n if (!raw) return null;\n const value = raw.trim().toLowerCase();\n if (value === EmailRecoveryModeHint.ZkEmail) return 'zk-email';\n if (value === EmailRecoveryModeHint.TeeEncrypted) return 'tee-encrypted';\n if (value === EmailRecoveryModeHint.OnchainPublic) return 'onchain-public';\n return null;\n}\n\nexport function extractRecoveryModeFromBody(emailBlob?: string): EmailRecoveryMode | null {\n if (!emailBlob) return null;\n\n const lines = emailBlob.split(/\\r?\\n/);\n const bodyStartIndex = lines.findIndex(line => line.trim() === '');\n if (bodyStartIndex === -1) return null;\n\n const bodyLines = lines.slice(bodyStartIndex + 1);\n const firstNonEmptyBodyLine = bodyLines.find(line => line.trim() !== '');\n if (!firstNonEmptyBodyLine) return null;\n\n const candidate = firstNonEmptyBodyLine.trim();\n const normalized = normalizeRecoveryMode(candidate);\n if (normalized) return normalized;\n\n const lower = candidate.toLowerCase();\n if (lower.includes(EmailRecoveryModeHint.ZkEmail)) return 'zk-email';\n if (lower.includes(EmailRecoveryModeHint.TeeEncrypted)) return 'tee-encrypted';\n if (lower.includes(EmailRecoveryModeHint.OnchainPublic)) return 'onchain-public';\n\n return null;\n}\n\ntype HeaderValue = string | string[] | undefined;\ntype HeadersLike = Headers | Record<string, HeaderValue> | undefined;\n\nexport type RecoverEmailParseResult =\n | { ok: true; accountId: string; emailBlob: string; explicitMode?: string }\n | { ok: false; status: number; code: string; message: string };\n\nfunction getHeader(headers: HeadersLike, name: string): string | undefined {\n if (!headers) return undefined;\n\n const maybeHeaders = headers as any;\n if (typeof maybeHeaders.get === 'function') {\n const v = maybeHeaders.get(name);\n return (typeof v === 'string') ? v : undefined;\n }\n\n const record = headers as Record<string, HeaderValue>;\n const v = record[name.toLowerCase()] ?? record[name];\n if (Array.isArray(v)) return (typeof v[0] === 'string') ? v[0] : undefined;\n return (typeof v === 'string') ? v : undefined;\n}\n\nfunction parseExplicitMode(body: unknown, headers?: HeadersLike): string | undefined {\n const modeFromBody =\n (typeof (body as any)?.explicitMode === 'string' ? String((body as any).explicitMode) : '') ||\n (typeof (body as any)?.explicit_mode === 'string' ? String((body as any).explicit_mode) : '');\n const modeFromHeader = getHeader(headers, 'x-email-recovery-mode') || getHeader(headers, 'x-recovery-mode') || '';\n const raw = (modeFromBody || modeFromHeader).trim();\n return raw ? raw : undefined;\n}\n\nexport function parseRecoverEmailRequest(body: unknown, opts: { headers?: HeadersLike } = {}): RecoverEmailParseResult {\n const explicitMode = parseExplicitMode(body, opts.headers);\n\n const normalized = normalizeForwardableEmailPayload(body);\n if (!normalized.ok) {\n return { ok: false, status: 400, code: normalized.code, message: normalized.message };\n }\n\n const payload = normalized.payload;\n const emailBlob = payload.raw || '';\n const emailHeaders = payload.headers || {};\n\n const subjectHeader = emailHeaders['subject'];\n const parsedAccountId = parseAccountIdFromSubject(subjectHeader || emailBlob);\n const headerAccountId = String(emailHeaders['x-near-account-id'] || emailHeaders['x-account-id'] || '').trim();\n const accountId = (parsedAccountId || headerAccountId || '').trim();\n\n if (!accountId) {\n return { ok: false, status: 400, code: 'missing_account', message: 'x-near-account-id header is required' };\n }\n if (!emailBlob) {\n return { ok: false, status: 400, code: 'missing_email', message: 'raw email blob is required' };\n }\n\n return { ok: true, accountId, emailBlob, explicitMode };\n}\n\nconst EMAIL_ADDRESS_REGEX =\n /([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)/;\n\nexport function canonicalizeEmail(input: string): string {\n const raw = String(input || '').trim();\n if (!raw) return '';\n\n // Handle cases where a full header line is passed in (e.g. \"From: ...\").\n const withoutHeaderName = raw.replace(/^[a-z0-9-]+\\s*:\\s*/i, '').trim();\n\n // Prefer the common \"Name <email@domain>\" format when present, but still\n // validate/extract the actual address via regex.\n const angleMatch = withoutHeaderName.match(/<([^>]+)>/);\n const candidates = [\n angleMatch?.[1],\n withoutHeaderName,\n ].filter((v): v is string => typeof v === 'string' && v.length > 0);\n\n for (const candidate of candidates) {\n const cleaned = candidate.replace(/^mailto:\\s*/i, '');\n const match = cleaned.match(EMAIL_ADDRESS_REGEX);\n if (match?.[1]) {\n return match[1].trim().toLowerCase();\n }\n }\n\n return withoutHeaderName.toLowerCase();\n}\n\nexport function parseHeaderValue(rawEmail: string, name: string): string | undefined {\n try {\n const raw = String(rawEmail || '');\n if (!raw) return undefined;\n\n const lines = raw.split(/\\r?\\n/);\n const headerLines: string[] = [];\n\n // Only consider the header section (until the first blank line).\n for (const line of lines) {\n if (line.trim() === '') break;\n\n // RFC822 header folding: lines starting with whitespace continue previous header.\n if (/^\\s/.test(line) && headerLines.length > 0) {\n headerLines[headerLines.length - 1] += ` ${line.trim()}`;\n continue;\n }\n\n headerLines.push(line);\n }\n\n const headerName = name.trim();\n if (!headerName) return undefined;\n\n const re = new RegExp(`^${headerName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\s*:`, 'i');\n const found = headerLines.find((l) => re.test(l));\n if (!found) return undefined;\n\n const idx = found.indexOf(':');\n const value = idx >= 0 ? found.slice(idx + 1).trim() : '';\n return value || undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function parseRecoverSubjectBindings(\n rawEmail: string\n): { requestId: string; accountId: string; newPublicKey: string } | null {\n // Accept either a full RFC822 email or a bare Subject value.\n let subjectText = (parseHeaderValue(rawEmail, 'subject') || String(rawEmail || '')).trim();\n if (!subjectText) return null;\n\n // Strip common reply/forward prefixes.\n subjectText = subjectText.replace(/^(re|fwd):\\s*/i, '').trim();\n if (!subjectText) return null;\n\n // Strict format:\n // \"recover-<request_id> <accountId> ed25519:<pk>\"\n const match = subjectText.match(\n /^recover-([A-Za-z0-9]{6})\\s+([^\\s]+)\\s+ed25519:([^\\s]+)\\s*$/i\n );\n if (!match) return null;\n\n const [, requestId, accountId, newPublicKey] = match;\n return { requestId, accountId, newPublicKey };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"emailParsers.js","names":["headerLines: string[]"],"sources":["../../../../src/server/email-recovery/emailParsers.ts"],"sourcesContent":["import type { EmailRecoveryMode } from './types';\nimport { normalizeForwardableEmailPayload, parseAccountIdFromSubject } from './zkEmail';\nimport { ensureEd25519Prefix } from '../../core/nearCrypto';\n\nexport enum EmailRecoveryModeHint {\n ZkEmail = 'zk-email',\n TeeEncrypted = 'tee-encrypted',\n OnchainPublic = 'onchain-public',\n}\n\nexport function normalizeRecoveryMode(raw: string | undefined | null): EmailRecoveryMode | null {\n if (!raw) return null;\n const value = raw.trim().toLowerCase();\n if (value === EmailRecoveryModeHint.ZkEmail) return 'zk-email';\n if (value === EmailRecoveryModeHint.TeeEncrypted) return 'tee-encrypted';\n if (value === EmailRecoveryModeHint.OnchainPublic) return 'onchain-public';\n return null;\n}\n\nexport function extractRecoveryModeFromBody(emailBlob?: string): EmailRecoveryMode | null {\n if (!emailBlob) return null;\n\n const lines = emailBlob.split(/\\r?\\n/);\n const bodyStartIndex = lines.findIndex(line => line.trim() === '');\n if (bodyStartIndex === -1) return null;\n\n const bodyLines = lines.slice(bodyStartIndex + 1);\n const firstNonEmptyBodyLine = bodyLines.find(line => line.trim() !== '');\n if (!firstNonEmptyBodyLine) return null;\n\n const candidate = firstNonEmptyBodyLine.trim();\n const normalized = normalizeRecoveryMode(candidate);\n if (normalized) return normalized;\n\n const lower = candidate.toLowerCase();\n if (lower.includes(EmailRecoveryModeHint.ZkEmail)) return 'zk-email';\n if (lower.includes(EmailRecoveryModeHint.TeeEncrypted)) return 'tee-encrypted';\n if (lower.includes(EmailRecoveryModeHint.OnchainPublic)) return 'onchain-public';\n\n return null;\n}\n\ntype HeaderValue = string | string[] | undefined;\ntype HeadersLike = Headers | Record<string, HeaderValue> | undefined;\n\nexport type RecoverEmailParseResult =\n | { ok: true; accountId: string; emailBlob: string; explicitMode?: string }\n | { ok: false; status: number; code: string; message: string };\n\nfunction getHeader(headers: HeadersLike, name: string): string | undefined {\n if (!headers) return undefined;\n\n const maybeHeaders = headers as any;\n if (typeof maybeHeaders.get === 'function') {\n const v = maybeHeaders.get(name);\n return (typeof v === 'string') ? v : undefined;\n }\n\n const record = headers as Record<string, HeaderValue>;\n const v = record[name.toLowerCase()] ?? record[name];\n if (Array.isArray(v)) return (typeof v[0] === 'string') ? v[0] : undefined;\n return (typeof v === 'string') ? v : undefined;\n}\n\nfunction parseExplicitMode(body: unknown, headers?: HeadersLike): string | undefined {\n const modeFromBody =\n (typeof (body as any)?.explicitMode === 'string' ? String((body as any).explicitMode) : '') ||\n (typeof (body as any)?.explicit_mode === 'string' ? String((body as any).explicit_mode) : '');\n const modeFromHeader = getHeader(headers, 'x-email-recovery-mode') || getHeader(headers, 'x-recovery-mode') || '';\n const raw = (modeFromBody || modeFromHeader).trim();\n return raw ? raw : undefined;\n}\n\nexport function parseRecoverEmailRequest(body: unknown, opts: { headers?: HeadersLike } = {}): RecoverEmailParseResult {\n const explicitMode = parseExplicitMode(body, opts.headers);\n\n const normalized = normalizeForwardableEmailPayload(body);\n if (!normalized.ok) {\n return { ok: false, status: 400, code: normalized.code, message: normalized.message };\n }\n\n const payload = normalized.payload;\n const emailBlob = payload.raw || '';\n const emailHeaders = payload.headers || {};\n\n const subjectHeader = emailHeaders['subject'];\n const parsedAccountId = parseAccountIdFromSubject(subjectHeader || emailBlob);\n const headerAccountId = String(emailHeaders['x-near-account-id'] || emailHeaders['x-account-id'] || '').trim();\n const accountId = (parsedAccountId || headerAccountId || '').trim();\n\n if (!accountId) {\n return { ok: false, status: 400, code: 'missing_account', message: 'x-near-account-id header is required' };\n }\n if (!emailBlob) {\n return { ok: false, status: 400, code: 'missing_email', message: 'raw email blob is required' };\n }\n\n return { ok: true, accountId, emailBlob, explicitMode };\n}\n\nconst EMAIL_ADDRESS_REGEX =\n /([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)/;\n\nexport function canonicalizeEmail(input: string): string {\n const raw = String(input || '').trim();\n if (!raw) return '';\n\n // Handle cases where a full header line is passed in (e.g. \"From: ...\").\n const withoutHeaderName = raw.replace(/^[a-z0-9-]+\\s*:\\s*/i, '').trim();\n\n // Prefer the common \"Name <email@domain>\" format when present, but still\n // validate/extract the actual address via regex.\n const angleMatch = withoutHeaderName.match(/<([^>]+)>/);\n const candidates = [\n angleMatch?.[1],\n withoutHeaderName,\n ].filter((v): v is string => typeof v === 'string' && v.length > 0);\n\n for (const candidate of candidates) {\n const cleaned = candidate.replace(/^mailto:\\s*/i, '');\n const match = cleaned.match(EMAIL_ADDRESS_REGEX);\n if (match?.[1]) {\n return match[1].trim().toLowerCase();\n }\n }\n\n return withoutHeaderName.toLowerCase();\n}\n\nexport function parseHeaderValue(rawEmail: string, name: string): string | undefined {\n try {\n const raw = String(rawEmail || '');\n if (!raw) return undefined;\n\n const lines = raw.split(/\\r?\\n/);\n const headerLines: string[] = [];\n\n // Only consider the header section (until the first blank line).\n for (const line of lines) {\n if (line.trim() === '') break;\n\n // RFC822 header folding: lines starting with whitespace continue previous header.\n if (/^\\s/.test(line) && headerLines.length > 0) {\n headerLines[headerLines.length - 1] += ` ${line.trim()}`;\n continue;\n }\n\n headerLines.push(line);\n }\n\n const headerName = name.trim();\n if (!headerName) return undefined;\n\n const re = new RegExp(`^${headerName.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\s*:`, 'i');\n const found = headerLines.find((l) => re.test(l));\n if (!found) return undefined;\n\n const idx = found.indexOf(':');\n const value = idx >= 0 ? found.slice(idx + 1).trim() : '';\n return value || undefined;\n } catch {\n return undefined;\n }\n}\n\nexport function parseRecoverSubjectBindings(\n rawEmail: string\n): { requestId: string; accountId: string; newPublicKey: string } | null {\n // Accept either a full RFC822 email or a bare Subject value.\n let subjectText = (parseHeaderValue(rawEmail, 'subject') || String(rawEmail || '')).trim();\n if (!subjectText) return null;\n\n // Strip common reply/forward prefixes.\n subjectText = subjectText.replace(/^(re|fwd):\\s*/i, '').trim();\n if (!subjectText) return null;\n\n // Strict format:\n // \"recover-<request_id> <accountId> ed25519:<pk>\"\n const match = subjectText.match(\n /^recover-([A-Za-z0-9]{6})\\s+([^\\s]+)\\s+ed25519:([^\\s]+)\\s*$/i\n );\n if (!match) return null;\n\n const [, requestId, accountId, newPublicKey] = match;\n return { requestId, accountId, newPublicKey: ensureEd25519Prefix(newPublicKey) };\n}\n"],"mappings":";;;AAIA,IAAY,0EAAL;AACL;AACA;AACA;;;AAGF,SAAgB,sBAAsB,KAA0D;AAC9F,KAAI,CAAC,IAAK,QAAO;CACjB,MAAM,QAAQ,IAAI,OAAO;AACzB,KAAI,UAAU,sBAAsB,QAAS,QAAO;AACpD,KAAI,UAAU,sBAAsB,aAAc,QAAO;AACzD,KAAI,UAAU,sBAAsB,cAAe,QAAO;AAC1D,QAAO;;AAGT,SAAgB,4BAA4B,WAA8C;AACxF,KAAI,CAAC,UAAW,QAAO;CAEvB,MAAM,QAAQ,UAAU,MAAM;CAC9B,MAAM,iBAAiB,MAAM,WAAU,SAAQ,KAAK,WAAW;AAC/D,KAAI,mBAAmB,GAAI,QAAO;CAElC,MAAM,YAAY,MAAM,MAAM,iBAAiB;CAC/C,MAAM,wBAAwB,UAAU,MAAK,SAAQ,KAAK,WAAW;AACrE,KAAI,CAAC,sBAAuB,QAAO;CAEnC,MAAM,YAAY,sBAAsB;CACxC,MAAM,aAAa,sBAAsB;AACzC,KAAI,WAAY,QAAO;CAEvB,MAAM,QAAQ,UAAU;AACxB,KAAI,MAAM,SAAS,sBAAsB,SAAU,QAAO;AAC1D,KAAI,MAAM,SAAS,sBAAsB,cAAe,QAAO;AAC/D,KAAI,MAAM,SAAS,sBAAsB,eAAgB,QAAO;AAEhE,QAAO;;AA6DT,MAAM,sBACJ;AAEF,SAAgB,kBAAkB,OAAuB;CACvD,MAAM,MAAM,OAAO,SAAS,IAAI;AAChC,KAAI,CAAC,IAAK,QAAO;CAGjB,MAAM,oBAAoB,IAAI,QAAQ,uBAAuB,IAAI;CAIjE,MAAM,aAAa,kBAAkB,MAAM;CAC3C,MAAM,aAAa,CACjB,aAAa,IACb,mBACA,QAAQ,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS;AAEjE,MAAK,MAAM,aAAa,YAAY;EAClC,MAAM,UAAU,UAAU,QAAQ,gBAAgB;EAClD,MAAM,QAAQ,QAAQ,MAAM;AAC5B,MAAI,QAAQ,GACV,QAAO,MAAM,GAAG,OAAO;;AAI3B,QAAO,kBAAkB;;AAG3B,SAAgB,iBAAiB,UAAkB,MAAkC;AACnF,KAAI;EACF,MAAM,MAAM,OAAO,YAAY;AAC/B,MAAI,CAAC,IAAK,QAAO;EAEjB,MAAM,QAAQ,IAAI,MAAM;EACxB,MAAMA,cAAwB;AAG9B,OAAK,MAAM,QAAQ,OAAO;AACxB,OAAI,KAAK,WAAW,GAAI;AAGxB,OAAI,MAAM,KAAK,SAAS,YAAY,SAAS,GAAG;AAC9C,gBAAY,YAAY,SAAS,MAAM,IAAI,KAAK;AAChD;;AAGF,eAAY,KAAK;;EAGnB,MAAM,aAAa,KAAK;AACxB,MAAI,CAAC,WAAY,QAAO;EAExB,MAAM,KAAK,IAAI,OAAO,IAAI,WAAW,QAAQ,uBAAuB,QAAQ,QAAQ;EACpF,MAAM,QAAQ,YAAY,MAAM,MAAM,GAAG,KAAK;AAC9C,MAAI,CAAC,MAAO,QAAO;EAEnB,MAAM,MAAM,MAAM,QAAQ;EAC1B,MAAM,QAAQ,OAAO,IAAI,MAAM,MAAM,MAAM,GAAG,SAAS;AACvD,SAAO,SAAS;SACV;AACN,SAAO;;;AAIX,SAAgB,4BACd,UACuE;CAEvE,IAAI,eAAe,iBAAiB,UAAU,cAAc,OAAO,YAAY,KAAK;AACpF,KAAI,CAAC,YAAa,QAAO;AAGzB,eAAc,YAAY,QAAQ,kBAAkB,IAAI;AACxD,KAAI,CAAC,YAAa,QAAO;CAIzB,MAAM,QAAQ,YAAY,MACxB;AAEF,KAAI,CAAC,MAAO,QAAO;CAEnB,MAAM,GAAG,WAAW,WAAW,gBAAgB;AAC/C,QAAO;EAAE;EAAW;EAAW,cAAc,oBAAoB"}
|
|
@@ -16,8 +16,8 @@ import { decryptEmailForOutlayerTestOnly, deriveTestX25519KeypairFromSeed } from
|
|
|
16
16
|
* - Encrypting raw RFC822 emails with encryptEmailForOutlayer, binding an AEAD context
|
|
17
17
|
* `{ account_id, network_id, payer_account_id }`,
|
|
18
18
|
* - Calling the per-account EmailRecoverer contract with:
|
|
19
|
-
* - `verify_encrypted_email_and_recover(encrypted_email_blob, aead_context, expected_hashed_email, expected_new_public_key)` for DKIM/TEE,
|
|
20
|
-
* - `verify_zkemail_and_recover` for zk-email recovery,
|
|
19
|
+
* - `verify_encrypted_email_and_recover(encrypted_email_blob, aead_context, expected_hashed_email, expected_new_public_key, request_id)` for DKIM/TEE,
|
|
20
|
+
* - `verify_zkemail_and_recover(..., request_id)` for zk-email recovery,
|
|
21
21
|
* - Performing legacy plaintext on-chain verification via `verify_email_onchain_and_recover`
|
|
22
22
|
* for backwards compatibility only.
|
|
23
23
|
*/
|
|
@@ -149,10 +149,9 @@ var EmailRecoveryService = class {
|
|
|
149
149
|
* - Calls the per-account EmailRecoverer contract's
|
|
150
150
|
* `verify_encrypted_email_and_recover` entrypoint on the user's account.
|
|
151
151
|
*
|
|
152
|
-
* The per-account EmailRecoverer
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
* to observe success/failure.
|
|
152
|
+
* The per-account EmailRecoverer records a pollable attempt keyed by
|
|
153
|
+
* `request_id` (parsed from the email Subject) so the frontend can observe
|
|
154
|
+
* success/failure by polling `EmailRecoverer.get_recovery_attempt(request_id)`.
|
|
156
155
|
*/
|
|
157
156
|
async verifyEncryptedEmailAndRecover(request) {
|
|
158
157
|
const accountId = (request.accountId || "").trim();
|
|
@@ -334,6 +333,7 @@ var EmailRecoveryService = class {
|
|
|
334
333
|
public_inputs: proofResult.publicInputs,
|
|
335
334
|
account_id: bindings.accountId,
|
|
336
335
|
new_public_key: bindings.newPublicKey,
|
|
336
|
+
request_id: bindings.requestId,
|
|
337
337
|
from_email: bindings.fromEmail,
|
|
338
338
|
timestamp: bindings.timestamp
|
|
339
339
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["e: unknown","e: any","error: any"],"sources":["../../../../src/server/email-recovery/index.ts"],"sourcesContent":["import type { ActionArgsWasm } from '../../core/types/actions';\nimport { ActionType, validateActionArgsWasm } from '../../core/types/actions';\nimport { parseContractExecutionError } from '../core/errors';\nimport { generateZkEmailProofFromPayload, type ZkEmailProverClientOptions } from './zkEmail';\nimport { extractRecoveryModeFromBody, normalizeRecoveryMode } from './emailParsers';\nimport { encryptEmailForOutlayer } from './emailEncryptor';\nimport { buildEncryptedEmailRecoveryActions, buildOnchainEmailRecoveryActions, buildZkEmailRecoveryActions, sendEmailRecoveryTransaction, getOutlayerEncryptionPublicKey } from './rpcCalls';\nimport { ZkEmailProverClient } from './zkEmail/proverClient';\nimport { mapZkEmailRecoveryError, prepareZkEmailRecovery } from './zkEmail/recovery';\nimport { normalizeLogger, type NormalizedLogger } from '../core/logger';\nimport type {\n EmailRecoveryDispatchRequest,\n EmailRecoveryMode,\n EmailRecoveryRequest,\n EmailRecoveryResult,\n EmailRecoveryServiceDeps,\n} from './types';\n\nexport * from './emailEncryptor';\nexport * from './zkEmail';\nexport * from './zkEmail/recovery';\nexport * from './testHelpers';\nexport * from './types';\n\n/**\n * EmailRecoveryService encapsulates email recovery logic for the relayer.\n *\n * It currently orchestrates:\n * - Fetching and caching the Outlayer X25519 public key from the global EmailDKIMVerifier,\n * - Encrypting raw RFC822 emails with encryptEmailForOutlayer, binding an AEAD context\n * `{ account_id, network_id, payer_account_id }`,\n * - Calling the per-account EmailRecoverer contract with:\n * - `verify_encrypted_email_and_recover(encrypted_email_blob, aead_context, expected_hashed_email, expected_new_public_key)` for DKIM/TEE,\n * - `verify_zkemail_and_recover` for zk-email recovery,\n * - Performing legacy plaintext on-chain verification via `verify_email_onchain_and_recover`\n * for backwards compatibility only.\n */\nexport class EmailRecoveryService {\n private readonly deps: EmailRecoveryServiceDeps;\n private readonly logger: NormalizedLogger;\n private cachedOutlayerPk: Uint8Array | null = null;\n private zkEmailProverClient: ZkEmailProverClient | null = null;\n private zkEmailProverClientKey: string | null = null;\n\n constructor(deps: EmailRecoveryServiceDeps) {\n this.deps = deps;\n this.logger = normalizeLogger(deps.logger);\n }\n\n /**\n * Lightweight view of zk-email prover wiring for health/readiness endpoints.\n * This does not perform any network calls.\n */\n getZkEmailProverBaseUrl(): string | null {\n const baseUrl = String(this.deps.zkEmailProver?.baseUrl || '').trim().replace(/\\/+$/, '');\n return baseUrl ? baseUrl : null;\n }\n\n /**\n * Readiness check for zk-email prover.\n *\n * Returns `healthy: null` when zk-email prover is not configured.\n * Does not log; callers (routers) may decide how/when to log.\n */\n async checkZkEmailProverHealth(): Promise<{\n configured: boolean;\n baseUrl: string | null;\n healthy: boolean | null;\n errorCode?: string;\n message?: string;\n proverCauseCode?: string;\n proverCauseMessage?: string;\n }> {\n const baseUrl = this.getZkEmailProverBaseUrl();\n const opts = this.deps.zkEmailProver;\n if (!baseUrl || !opts) {\n return { configured: false, baseUrl: null, healthy: null };\n }\n\n try {\n const client = this.getZkEmailProverClient({ ...opts, baseUrl });\n await client.healthz();\n return { configured: true, baseUrl, healthy: true };\n } catch (e: unknown) {\n const mapped = mapZkEmailRecoveryError(e);\n return {\n configured: true,\n baseUrl,\n healthy: false,\n errorCode: mapped.errorCode,\n message: mapped.message,\n proverCauseCode: mapped.proverCauseCode,\n proverCauseMessage: mapped.proverCauseMessage,\n };\n }\n }\n\n private getZkEmailProverClient(opts: ZkEmailProverClientOptions): ZkEmailProverClient {\n const baseUrl = String(opts.baseUrl || '').replace(/\\/+$/, '');\n const timeoutMs = opts.timeoutMs ?? 60_000;\n const healthCheck = opts.healthCheck;\n const key = `${baseUrl}|${timeoutMs}|${healthCheck?.enabled ?? 'default'}|${healthCheck?.ttlMs ?? 'default'}|${healthCheck?.timeoutMs ?? 'default'}`;\n\n if (this.zkEmailProverClient && this.zkEmailProverClientKey === key) {\n return this.zkEmailProverClient;\n }\n\n const client = new ZkEmailProverClient(opts);\n this.zkEmailProverClient = client;\n this.zkEmailProverClientKey = key;\n return client;\n }\n\n private async getOutlayerEmailDkimPublicKey(): Promise<Uint8Array> {\n if (this.cachedOutlayerPk) {\n return this.cachedOutlayerPk;\n }\n const pk = await getOutlayerEncryptionPublicKey(this.deps);\n this.cachedOutlayerPk = pk;\n return pk;\n }\n\n /**\n * Determine recovery mode (zk-email | encrypted | onchain-public) from:\n * - explicit override (for programmatic callers),\n * - body markers inside the raw email,\n * falling back to tee-private for backwards compatibility.\n */\n private determineRecoveryMode(input: {\n explicitMode?: string;\n emailBlob?: string;\n }): EmailRecoveryMode {\n return (\n normalizeRecoveryMode(input.explicitMode) ??\n extractRecoveryModeFromBody(input.emailBlob) ??\n 'tee-encrypted'\n );\n }\n\n /**\n * Top-level dispatcher for email recovery modes.\n *\n * Usage from HTTP routes:\n * - Pass the full raw RFC822 email as `emailBlob` (including headers + body).\n * - Optionally include an explicit `explicitMode` override (`'zk-email' | 'tee-encrypted' | 'onchain-public'`).\n * - Otherwise, the first non-empty body line is parsed as a mode hint:\n * - `\"zk-email\"` → zk-email prover + per-account `verify_zkemail_and_recover`.\n * - `\"tee-encrypted\"` (or legacy `\"encrypted\"`) → per-account EmailRecoverer encrypted path (`verify_encrypted_email_and_recover`).\n * - `\"onchain-public\"` → currently routed to the same per-account encrypted path for backwards compatibility.\n * - If no hint is found, the mode defaults to `'tee-encrypted'`.\n */\n async requestEmailRecovery(request: EmailRecoveryDispatchRequest): Promise<EmailRecoveryResult> {\n const mode = this.determineRecoveryMode({\n explicitMode: request.explicitMode,\n emailBlob: request.emailBlob,\n });\n this.logger.debug('[email-recovery] requestEmailRecovery mode selected', {\n mode,\n accountId: request.accountId,\n });\n\n switch (mode) {\n case 'tee-encrypted':\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n case 'zk-email':\n return this.verifyZkemailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n case 'onchain-public':\n // Use the same encrypted/TEE path via per-account EmailRecoverer.\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n default:\n // Fallback to the TEE-encrypted path for forwards compatibility.\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n }\n }\n\n /**\n * Helper for encrypted DKIM-based email recovery:\n * - Encrypts the raw email blob for the Outlayer worker.\n * - Calls the per-account EmailRecoverer contract's\n * `verify_encrypted_email_and_recover` entrypoint on the user's account.\n *\n * The per-account EmailRecoverer then delegates to the global\n * EmailDKIMVerifier (TEE path), which stores a VerificationResult keyed by\n * request_id. The frontend polls EmailDKIMVerifier::get_verification_result(request_id)\n * to observe success/failure.\n */\n\t async verifyEncryptedEmailAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n\t const accountId = (request.accountId || '').trim();\n\t const emailBlob = request.emailBlob;\n\n\t if (!accountId) {\n\t const errMsg = 'accountId is required';\n\t return { success: false, error: errMsg, message: errMsg };\n\t }\n\t if (!emailBlob || typeof emailBlob !== 'string') {\n\t const errMsg = 'emailBlob (raw email) is required';\n\t return { success: false, error: errMsg, message: errMsg };\n\t }\n\n\t const { ensureSignerAndRelayerAccount } = this.deps;\n\n\t try {\n\t await ensureSignerAndRelayerAccount();\n\t } catch (e: any) {\n\t const msg = e?.message || 'Failed to initialize relayer account';\n\t return { success: false, error: msg, message: msg };\n\t }\n\n\t\t const recipientPk = await this.getOutlayerEmailDkimPublicKey();\n\t\t this.logger.debug('[email-recovery] encrypted using Outlayer public key', {\n\t\t accountId,\n\t\t outlayerPkLen: recipientPk.length,\n\t\t });\n\n\t const { actions, receiverId } = await buildEncryptedEmailRecoveryActions(this.deps, {\n\t accountId,\n\t emailBlob,\n\t recipientPk,\n\t encrypt: async ({ emailRaw, aeadContext, recipientPk: pk }) => {\n\t const { envelope } = await encryptEmailForOutlayer({\n\t emailRaw,\n\t aeadContext,\n\t recipientPk: pk,\n\t });\n\n\t\t this.logger.debug('[email-recovery] encrypted email envelope metadata', {\n\t\t accountId,\n\t\t aeadContextLen: aeadContext.length,\n\t\t envelope: {\n\t\t version: envelope.version,\n\t\t ephemeral_pub_len: envelope.ephemeral_pub?.length ?? 0,\n\t\t nonce_len: envelope.nonce?.length ?? 0,\n\t\t ciphertext_len: envelope.ciphertext?.length ?? 0,\n\t\t },\n\t\t });\n\n\t return { envelope };\n\t },\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `Encrypted email verification requested for ${accountId}`,\n\t });\n\t }\n\n /**\n * Legacy helper for plaintext/on-chain DKIM email verification + account recovery.\n * This path is deprecated in favor of the encrypted TEE path via\n * `verifyEncryptedEmailAndRecover` and is no longer used by\n * `requestEmailRecovery`.\n */\n\t async verifyEmailOnchainAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n const accountId = (request.accountId || '').trim();\n const emailBlob = request.emailBlob;\n\n if (!accountId) {\n let errMsg = 'accountId is required';\n return { success: false, error: errMsg, message: errMsg };\n }\n if (!emailBlob || typeof emailBlob !== 'string') {\n let errMsg = 'emailBlob (raw email) is required';\n return { success: false, error: errMsg, message: errMsg };\n }\n\n\t const { ensureSignerAndRelayerAccount } = this.deps;\n\n try {\n await ensureSignerAndRelayerAccount();\n } catch (e: any) {\n const msg = e?.message || 'Failed to initialize relayer account';\n return { success: false, error: msg, message: msg };\n }\n\n\t const { actions, receiverId } = await buildOnchainEmailRecoveryActions(this.deps, {\n\t accountId,\n\t emailBlob,\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `On-chain email verification requested for ${accountId}`,\n\t });\n }\n\n /**\n * Helper for zk-email recovery:\n * - Calls external zk-email prover with the raw email blob to obtain (proof, publicInputs).\n * - Extracts subject/header bindings (account_id, new_public_key, from_email, timestamp).\n * - Calls the per-account EmailRecoverer contract with verify_zkemail_and_recover.\n */\n\t async verifyZkemailAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n\t const accountId = (request.accountId || '').trim();\n\t const emailBlob = request.emailBlob;\n\n if (!accountId) {\n return {\n success: false,\n error: 'zkemail_missing_account_id',\n message: 'accountId is required',\n };\n }\n if (!emailBlob || typeof emailBlob !== 'string') {\n return {\n success: false,\n error: 'zkemail_missing_email_blob',\n message: 'emailBlob (raw email) is required',\n };\n }\n\n\t const { ensureSignerAndRelayerAccount, zkEmailProver } = this.deps;\n\n if (!zkEmailProver || !zkEmailProver.baseUrl) {\n this.logger.warn('[email-recovery] zk-email missing prover configuration', { accountId });\n return {\n success: false,\n error: 'zkemail_prover_not_configured',\n message: 'zk-email prover configuration is missing',\n };\n }\n\n const prepared = prepareZkEmailRecovery(emailBlob, accountId);\n if (!prepared.ok) {\n const log = {\n accountId,\n requestId: prepared.requestId,\n proverBaseUrl: zkEmailProver.baseUrl,\n errorCode: prepared.errorCode,\n errorMessage: prepared.message,\n accountIdSubject: prepared.subjectAccountId,\n };\n if (prepared.errorCode === 'zkemail_account_mismatch') {\n this.logger.warn('[email-recovery] zk-email account mismatch', log);\n } else {\n this.logger.warn('[email-recovery] zk-email recovery rejected', log);\n }\n return { success: false, error: prepared.errorCode, message: prepared.message };\n }\n\n const { payload, bindings } = prepared.prepared;\n\n try {\n await ensureSignerAndRelayerAccount();\n } catch (e: any) {\n const msg = e?.message || 'Failed to initialize relayer account';\n this.logger.error('[email-recovery] zk-email ensureSignerAndRelayerAccount failed', {\n accountId,\n requestId: bindings.requestId,\n error: msg,\n });\n return { success: false, error: 'zkemail_relayer_init_failed', message: msg };\n }\n\n\t try {\n\t const proverClient = this.getZkEmailProverClient(zkEmailProver);\n\t const proofResult = await generateZkEmailProofFromPayload(payload, proverClient);\n\n\t const contractArgs = {\n\t proof: proofResult.proof,\n\t public_inputs: proofResult.publicInputs,\n\t account_id: bindings.accountId,\n\t new_public_key: bindings.newPublicKey,\n\t from_email: bindings.fromEmail,\n\t timestamp: bindings.timestamp,\n\t };\n\n\t const { actions, receiverId } = await buildZkEmailRecoveryActions(this.deps, {\n\t accountId,\n\t contractArgs,\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `ZK-email recovery requested for ${accountId}`,\n\t });\n\t\t } catch (error: any) {\n const mapped = mapZkEmailRecoveryError(error);\n\n\t\t this.logger.error('[email-recovery] zk-email recovery error', {\n\t\t accountId,\n\t requestId: bindings.requestId,\n\t\t errorCode: mapped.errorCode,\n\t\t errorMessage: mapped.message,\n\t proverBaseUrl: zkEmailProver.baseUrl,\n\t proverCauseCode: mapped.proverCauseCode,\n\t proverCauseMessage: mapped.proverCauseMessage,\n\t\t });\n\n\t return {\n\t success: false,\n\t error: mapped.errorCode,\n\t message: mapped.message,\n\t };\n\t }\n\t }\n\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAqCA,IAAa,uBAAb,MAAkC;CAChC,AAAiB;CACjB,AAAiB;CACjB,AAAQ,mBAAsC;CAC9C,AAAQ,sBAAkD;CAC1D,AAAQ,yBAAwC;CAEhD,YAAY,MAAgC;AAC1C,OAAK,OAAO;AACZ,OAAK,SAAS,gBAAgB,KAAK;;;;;;CAOrC,0BAAyC;EACvC,MAAM,UAAU,OAAO,KAAK,KAAK,eAAe,WAAW,IAAI,OAAO,QAAQ,QAAQ;AACtF,SAAO,UAAU,UAAU;;;;;;;;CAS7B,MAAM,2BAQH;EACD,MAAM,UAAU,KAAK;EACrB,MAAM,OAAO,KAAK,KAAK;AACvB,MAAI,CAAC,WAAW,CAAC,KACf,QAAO;GAAE,YAAY;GAAO,SAAS;GAAM,SAAS;;AAGtD,MAAI;GACF,MAAM,SAAS,KAAK,uBAAuB;IAAE,GAAG;IAAM;;AACtD,SAAM,OAAO;AACb,UAAO;IAAE,YAAY;IAAM;IAAS,SAAS;;WACtCA,GAAY;GACnB,MAAM,SAAS,wBAAwB;AACvC,UAAO;IACL,YAAY;IACZ;IACA,SAAS;IACT,WAAW,OAAO;IAClB,SAAS,OAAO;IAChB,iBAAiB,OAAO;IACxB,oBAAoB,OAAO;;;;CAKjC,AAAQ,uBAAuB,MAAuD;EACpF,MAAM,UAAU,OAAO,KAAK,WAAW,IAAI,QAAQ,QAAQ;EAC3D,MAAM,YAAY,KAAK,aAAa;EACpC,MAAM,cAAc,KAAK;EACzB,MAAM,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,aAAa,WAAW,UAAU,GAAG,aAAa,SAAS,UAAU,GAAG,aAAa,aAAa;AAEzI,MAAI,KAAK,uBAAuB,KAAK,2BAA2B,IAC9D,QAAO,KAAK;EAGd,MAAM,SAAS,IAAI,oBAAoB;AACvC,OAAK,sBAAsB;AAC3B,OAAK,yBAAyB;AAC9B,SAAO;;CAGT,MAAc,gCAAqD;AACjE,MAAI,KAAK,iBACP,QAAO,KAAK;EAEd,MAAM,KAAK,MAAM,+BAA+B,KAAK;AACrD,OAAK,mBAAmB;AACxB,SAAO;;;;;;;;CAST,AAAQ,sBAAsB,OAGR;AACpB,SACE,sBAAsB,MAAM,iBAC5B,4BAA4B,MAAM,cAClC;;;;;;;;;;;;;;CAgBJ,MAAM,qBAAqB,SAAqE;EAC9F,MAAM,OAAO,KAAK,sBAAsB;GACtC,cAAc,QAAQ;GACtB,WAAW,QAAQ;;AAErB,OAAK,OAAO,MAAM,uDAAuD;GACvE;GACA,WAAW,QAAQ;;AAGrB,UAAQ,MAAR;GACE,KAAK,gBACH,QAAO,KAAK,+BAA+B;IACzC,WAAW,QAAQ;IACnB,WAAW,QAAQ;;GAEvB,KAAK,WACH,QAAO,KAAK,wBAAwB;IAClC,WAAW,QAAQ;IACnB,WAAW,QAAQ;;GAEvB,KAAK,iBAEH,QAAO,KAAK,+BAA+B;IACzC,WAAW,QAAQ;IACnB,WAAW,QAAQ;;GAEvB,QAEE,QAAO,KAAK,+BAA+B;IACzC,WAAW,QAAQ;IACnB,WAAW,QAAQ;;;;;;;;;;;;;;;CAgB1B,MAAM,+BAA+B,SAA6D;EAChG,MAAM,aAAa,QAAQ,aAAa,IAAI;EAC5C,MAAM,YAAY,QAAQ;AAE1B,MAAI,CAAC,WAAW;GACd,MAAM,SAAS;AACf,UAAO;IAAE,SAAS;IAAO,OAAO;IAAQ,SAAS;;;AAEnD,MAAI,CAAC,aAAa,OAAO,cAAc,UAAU;GAC/C,MAAM,SAAS;AACf,UAAO;IAAE,SAAS;IAAO,OAAO;IAAQ,SAAS;;;EAGnD,MAAM,EAAE,kCAAkC,KAAK;AAE/C,MAAI;AACF,SAAM;WACCC,GAAQ;GACf,MAAM,MAAM,GAAG,WAAW;AAC1B,UAAO;IAAE,SAAS;IAAO,OAAO;IAAK,SAAS;;;EAG/C,MAAM,cAAc,MAAM,KAAK;AAC/B,OAAK,OAAO,MAAM,wDAAwD;GACxE;GACA,eAAe,YAAY;;EAG9B,MAAM,EAAE,SAAS,eAAe,MAAM,mCAAmC,KAAK,MAAM;GAClF;GACA;GACA;GACA,SAAS,OAAO,EAAE,UAAU,aAAa,aAAa,SAAS;IAC7D,MAAM,EAAE,aAAa,MAAM,wBAAwB;KACjD;KACA;KACA,aAAa;;AAGd,SAAK,OAAO,MAAM,sDAAsD;KACtE;KACA,gBAAgB,YAAY;KAC5B,UAAU;MACR,SAAS,SAAS;MAClB,mBAAmB,SAAS,eAAe,UAAU;MACrD,WAAW,SAAS,OAAO,UAAU;MACrC,gBAAgB,SAAS,YAAY,UAAU;;;AAIpD,WAAO,EAAE;;;AAIb,SAAO,6BAA6B,KAAK,MAAM;GAC7C;GACA;GACA,OAAO,8CAA8C;;;;;;;;;CAUzD,MAAM,6BAA6B,SAA6D;EAC/F,MAAM,aAAa,QAAQ,aAAa,IAAI;EAC5C,MAAM,YAAY,QAAQ;AAE1B,MAAI,CAAC,WAAW;GACd,IAAI,SAAS;AACb,UAAO;IAAE,SAAS;IAAO,OAAO;IAAQ,SAAS;;;AAEnD,MAAI,CAAC,aAAa,OAAO,cAAc,UAAU;GAC/C,IAAI,SAAS;AACb,UAAO;IAAE,SAAS;IAAO,OAAO;IAAQ,SAAS;;;EAGlD,MAAM,EAAE,kCAAkC,KAAK;AAEhD,MAAI;AACF,SAAM;WACCA,GAAQ;GACf,MAAM,MAAM,GAAG,WAAW;AAC1B,UAAO;IAAE,SAAS;IAAO,OAAO;IAAK,SAAS;;;EAG/C,MAAM,EAAE,SAAS,eAAe,MAAM,iCAAiC,KAAK,MAAM;GAChF;GACA;;AAGF,SAAO,6BAA6B,KAAK,MAAM;GAC7C;GACA;GACA,OAAO,6CAA6C;;;;;;;;;CAUxD,MAAM,wBAAwB,SAA6D;EACzF,MAAM,aAAa,QAAQ,aAAa,IAAI;EAC5C,MAAM,YAAY,QAAQ;AAE3B,MAAI,CAAC,UACH,QAAO;GACL,SAAS;GACT,OAAO;GACP,SAAS;;AAGb,MAAI,CAAC,aAAa,OAAO,cAAc,SACrC,QAAO;GACL,SAAS;GACT,OAAO;GACP,SAAS;;EAIZ,MAAM,EAAE,+BAA+B,kBAAkB,KAAK;AAE/D,MAAI,CAAC,iBAAiB,CAAC,cAAc,SAAS;AAC5C,QAAK,OAAO,KAAK,0DAA0D,EAAE;AAC7E,UAAO;IACL,SAAS;IACT,OAAO;IACP,SAAS;;;EAIb,MAAM,WAAW,uBAAuB,WAAW;AACnD,MAAI,CAAC,SAAS,IAAI;GAChB,MAAM,MAAM;IACV;IACA,WAAW,SAAS;IACpB,eAAe,cAAc;IAC7B,WAAW,SAAS;IACpB,cAAc,SAAS;IACvB,kBAAkB,SAAS;;AAE7B,OAAI,SAAS,cAAc,2BACzB,MAAK,OAAO,KAAK,8CAA8C;OAE/D,MAAK,OAAO,KAAK,+CAA+C;AAElE,UAAO;IAAE,SAAS;IAAO,OAAO,SAAS;IAAW,SAAS,SAAS;;;EAGxE,MAAM,EAAE,SAAS,aAAa,SAAS;AAEvC,MAAI;AACF,SAAM;WACCA,GAAQ;GACf,MAAM,MAAM,GAAG,WAAW;AAC1B,QAAK,OAAO,MAAM,kEAAkE;IAClF;IACA,WAAW,SAAS;IACpB,OAAO;;AAET,UAAO;IAAE,SAAS;IAAO,OAAO;IAA+B,SAAS;;;AAGzE,MAAI;GACF,MAAM,eAAe,KAAK,uBAAuB;GACjD,MAAM,cAAc,MAAM,gCAAgC,SAAS;GAEnE,MAAM,eAAe;IACnB,OAAO,YAAY;IACnB,eAAe,YAAY;IAC3B,YAAY,SAAS;IACrB,gBAAgB,SAAS;IACzB,YAAY,SAAS;IACrB,WAAW,SAAS;;GAGtB,MAAM,EAAE,SAAS,eAAe,MAAM,4BAA4B,KAAK,MAAM;IAC3E;IACA;;AAGF,UAAO,6BAA6B,KAAK,MAAM;IAC7C;IACA;IACA,OAAO,mCAAmC;;WAEpCC,OAAY;GACnB,MAAM,SAAS,wBAAwB;AAEvC,QAAK,OAAO,MAAM,4CAA4C;IAC5D;IACC,WAAW,SAAS;IACrB,WAAW,OAAO;IAClB,cAAc,OAAO;IACpB,eAAe,cAAc;IAC7B,iBAAiB,OAAO;IACxB,oBAAoB,OAAO;;AAG/B,UAAO;IACL,SAAS;IACT,OAAO,OAAO;IACd,SAAS,OAAO"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["e: unknown","e: any","error: any"],"sources":["../../../../src/server/email-recovery/index.ts"],"sourcesContent":["import type { ActionArgsWasm } from '../../core/types/actions';\nimport { ActionType, validateActionArgsWasm } from '../../core/types/actions';\nimport { parseContractExecutionError } from '../core/errors';\nimport { generateZkEmailProofFromPayload, type ZkEmailProverClientOptions } from './zkEmail';\nimport { extractRecoveryModeFromBody, normalizeRecoveryMode } from './emailParsers';\nimport { encryptEmailForOutlayer } from './emailEncryptor';\nimport { buildEncryptedEmailRecoveryActions, buildOnchainEmailRecoveryActions, buildZkEmailRecoveryActions, sendEmailRecoveryTransaction, getOutlayerEncryptionPublicKey } from './rpcCalls';\nimport { ZkEmailProverClient } from './zkEmail/proverClient';\nimport { mapZkEmailRecoveryError, prepareZkEmailRecovery } from './zkEmail/recovery';\nimport { normalizeLogger, type NormalizedLogger } from '../core/logger';\nimport type {\n EmailRecoveryDispatchRequest,\n EmailRecoveryMode,\n EmailRecoveryRequest,\n EmailRecoveryResult,\n EmailRecoveryServiceDeps,\n} from './types';\n\nexport * from './emailEncryptor';\nexport * from './zkEmail';\nexport * from './zkEmail/recovery';\nexport * from './testHelpers';\nexport * from './types';\n\n/**\n * EmailRecoveryService encapsulates email recovery logic for the relayer.\n *\n * It currently orchestrates:\n * - Fetching and caching the Outlayer X25519 public key from the global EmailDKIMVerifier,\n * - Encrypting raw RFC822 emails with encryptEmailForOutlayer, binding an AEAD context\n * `{ account_id, network_id, payer_account_id }`,\n * - Calling the per-account EmailRecoverer contract with:\n * - `verify_encrypted_email_and_recover(encrypted_email_blob, aead_context, expected_hashed_email, expected_new_public_key, request_id)` for DKIM/TEE,\n * - `verify_zkemail_and_recover(..., request_id)` for zk-email recovery,\n * - Performing legacy plaintext on-chain verification via `verify_email_onchain_and_recover`\n * for backwards compatibility only.\n */\nexport class EmailRecoveryService {\n private readonly deps: EmailRecoveryServiceDeps;\n private readonly logger: NormalizedLogger;\n private cachedOutlayerPk: Uint8Array | null = null;\n private zkEmailProverClient: ZkEmailProverClient | null = null;\n private zkEmailProverClientKey: string | null = null;\n\n constructor(deps: EmailRecoveryServiceDeps) {\n this.deps = deps;\n this.logger = normalizeLogger(deps.logger);\n }\n\n /**\n * Lightweight view of zk-email prover wiring for health/readiness endpoints.\n * This does not perform any network calls.\n */\n getZkEmailProverBaseUrl(): string | null {\n const baseUrl = String(this.deps.zkEmailProver?.baseUrl || '').trim().replace(/\\/+$/, '');\n return baseUrl ? baseUrl : null;\n }\n\n /**\n * Readiness check for zk-email prover.\n *\n * Returns `healthy: null` when zk-email prover is not configured.\n * Does not log; callers (routers) may decide how/when to log.\n */\n async checkZkEmailProverHealth(): Promise<{\n configured: boolean;\n baseUrl: string | null;\n healthy: boolean | null;\n errorCode?: string;\n message?: string;\n proverCauseCode?: string;\n proverCauseMessage?: string;\n }> {\n const baseUrl = this.getZkEmailProverBaseUrl();\n const opts = this.deps.zkEmailProver;\n if (!baseUrl || !opts) {\n return { configured: false, baseUrl: null, healthy: null };\n }\n\n try {\n const client = this.getZkEmailProverClient({ ...opts, baseUrl });\n await client.healthz();\n return { configured: true, baseUrl, healthy: true };\n } catch (e: unknown) {\n const mapped = mapZkEmailRecoveryError(e);\n return {\n configured: true,\n baseUrl,\n healthy: false,\n errorCode: mapped.errorCode,\n message: mapped.message,\n proverCauseCode: mapped.proverCauseCode,\n proverCauseMessage: mapped.proverCauseMessage,\n };\n }\n }\n\n private getZkEmailProverClient(opts: ZkEmailProverClientOptions): ZkEmailProverClient {\n const baseUrl = String(opts.baseUrl || '').replace(/\\/+$/, '');\n const timeoutMs = opts.timeoutMs ?? 60_000;\n const healthCheck = opts.healthCheck;\n const key = `${baseUrl}|${timeoutMs}|${healthCheck?.enabled ?? 'default'}|${healthCheck?.ttlMs ?? 'default'}|${healthCheck?.timeoutMs ?? 'default'}`;\n\n if (this.zkEmailProverClient && this.zkEmailProverClientKey === key) {\n return this.zkEmailProverClient;\n }\n\n const client = new ZkEmailProverClient(opts);\n this.zkEmailProverClient = client;\n this.zkEmailProverClientKey = key;\n return client;\n }\n\n private async getOutlayerEmailDkimPublicKey(): Promise<Uint8Array> {\n if (this.cachedOutlayerPk) {\n return this.cachedOutlayerPk;\n }\n const pk = await getOutlayerEncryptionPublicKey(this.deps);\n this.cachedOutlayerPk = pk;\n return pk;\n }\n\n /**\n * Determine recovery mode (zk-email | encrypted | onchain-public) from:\n * - explicit override (for programmatic callers),\n * - body markers inside the raw email,\n * falling back to tee-private for backwards compatibility.\n */\n private determineRecoveryMode(input: {\n explicitMode?: string;\n emailBlob?: string;\n }): EmailRecoveryMode {\n return (\n normalizeRecoveryMode(input.explicitMode) ??\n extractRecoveryModeFromBody(input.emailBlob) ??\n 'tee-encrypted'\n );\n }\n\n /**\n * Top-level dispatcher for email recovery modes.\n *\n * Usage from HTTP routes:\n * - Pass the full raw RFC822 email as `emailBlob` (including headers + body).\n * - Optionally include an explicit `explicitMode` override (`'zk-email' | 'tee-encrypted' | 'onchain-public'`).\n * - Otherwise, the first non-empty body line is parsed as a mode hint:\n * - `\"zk-email\"` → zk-email prover + per-account `verify_zkemail_and_recover`.\n * - `\"tee-encrypted\"` (or legacy `\"encrypted\"`) → per-account EmailRecoverer encrypted path (`verify_encrypted_email_and_recover`).\n * - `\"onchain-public\"` → currently routed to the same per-account encrypted path for backwards compatibility.\n * - If no hint is found, the mode defaults to `'tee-encrypted'`.\n */\n async requestEmailRecovery(request: EmailRecoveryDispatchRequest): Promise<EmailRecoveryResult> {\n const mode = this.determineRecoveryMode({\n explicitMode: request.explicitMode,\n emailBlob: request.emailBlob,\n });\n this.logger.debug('[email-recovery] requestEmailRecovery mode selected', {\n mode,\n accountId: request.accountId,\n });\n\n switch (mode) {\n case 'tee-encrypted':\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n case 'zk-email':\n return this.verifyZkemailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n case 'onchain-public':\n // Use the same encrypted/TEE path via per-account EmailRecoverer.\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n default:\n // Fallback to the TEE-encrypted path for forwards compatibility.\n return this.verifyEncryptedEmailAndRecover({\n accountId: request.accountId,\n emailBlob: request.emailBlob,\n });\n }\n }\n\n /**\n * Helper for encrypted DKIM-based email recovery:\n * - Encrypts the raw email blob for the Outlayer worker.\n * - Calls the per-account EmailRecoverer contract's\n * `verify_encrypted_email_and_recover` entrypoint on the user's account.\n *\n * The per-account EmailRecoverer records a pollable attempt keyed by\n * `request_id` (parsed from the email Subject) so the frontend can observe\n * success/failure by polling `EmailRecoverer.get_recovery_attempt(request_id)`.\n */\n\t async verifyEncryptedEmailAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n\t const accountId = (request.accountId || '').trim();\n\t const emailBlob = request.emailBlob;\n\n\t if (!accountId) {\n\t const errMsg = 'accountId is required';\n\t return { success: false, error: errMsg, message: errMsg };\n\t }\n\t if (!emailBlob || typeof emailBlob !== 'string') {\n\t const errMsg = 'emailBlob (raw email) is required';\n\t return { success: false, error: errMsg, message: errMsg };\n\t }\n\n\t const { ensureSignerAndRelayerAccount } = this.deps;\n\n\t try {\n\t await ensureSignerAndRelayerAccount();\n\t } catch (e: any) {\n\t const msg = e?.message || 'Failed to initialize relayer account';\n\t return { success: false, error: msg, message: msg };\n\t }\n\n\t\t const recipientPk = await this.getOutlayerEmailDkimPublicKey();\n\t\t this.logger.debug('[email-recovery] encrypted using Outlayer public key', {\n\t\t accountId,\n\t\t outlayerPkLen: recipientPk.length,\n\t\t });\n\n\t const { actions, receiverId } = await buildEncryptedEmailRecoveryActions(this.deps, {\n\t accountId,\n\t emailBlob,\n\t recipientPk,\n\t encrypt: async ({ emailRaw, aeadContext, recipientPk: pk }) => {\n\t const { envelope } = await encryptEmailForOutlayer({\n\t emailRaw,\n\t aeadContext,\n\t recipientPk: pk,\n\t });\n\n\t\t this.logger.debug('[email-recovery] encrypted email envelope metadata', {\n\t\t accountId,\n\t\t aeadContextLen: aeadContext.length,\n\t\t envelope: {\n\t\t version: envelope.version,\n\t\t ephemeral_pub_len: envelope.ephemeral_pub?.length ?? 0,\n\t\t nonce_len: envelope.nonce?.length ?? 0,\n\t\t ciphertext_len: envelope.ciphertext?.length ?? 0,\n\t\t },\n\t\t });\n\n\t return { envelope };\n\t },\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `Encrypted email verification requested for ${accountId}`,\n\t });\n\t }\n\n /**\n * Legacy helper for plaintext/on-chain DKIM email verification + account recovery.\n * This path is deprecated in favor of the encrypted TEE path via\n * `verifyEncryptedEmailAndRecover` and is no longer used by\n * `requestEmailRecovery`.\n */\n\t async verifyEmailOnchainAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n const accountId = (request.accountId || '').trim();\n const emailBlob = request.emailBlob;\n\n if (!accountId) {\n let errMsg = 'accountId is required';\n return { success: false, error: errMsg, message: errMsg };\n }\n if (!emailBlob || typeof emailBlob !== 'string') {\n let errMsg = 'emailBlob (raw email) is required';\n return { success: false, error: errMsg, message: errMsg };\n }\n\n\t const { ensureSignerAndRelayerAccount } = this.deps;\n\n try {\n await ensureSignerAndRelayerAccount();\n } catch (e: any) {\n const msg = e?.message || 'Failed to initialize relayer account';\n return { success: false, error: msg, message: msg };\n }\n\n\t const { actions, receiverId } = await buildOnchainEmailRecoveryActions(this.deps, {\n\t accountId,\n\t emailBlob,\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `On-chain email verification requested for ${accountId}`,\n\t });\n }\n\n /**\n * Helper for zk-email recovery:\n * - Calls external zk-email prover with the raw email blob to obtain (proof, publicInputs).\n * - Extracts subject/header bindings (account_id, new_public_key, from_email, timestamp).\n * - Calls the per-account EmailRecoverer contract with verify_zkemail_and_recover.\n */\n\t async verifyZkemailAndRecover(request: EmailRecoveryRequest): Promise<EmailRecoveryResult> {\n\t const accountId = (request.accountId || '').trim();\n\t const emailBlob = request.emailBlob;\n\n if (!accountId) {\n return {\n success: false,\n error: 'zkemail_missing_account_id',\n message: 'accountId is required',\n };\n }\n if (!emailBlob || typeof emailBlob !== 'string') {\n return {\n success: false,\n error: 'zkemail_missing_email_blob',\n message: 'emailBlob (raw email) is required',\n };\n }\n\n\t const { ensureSignerAndRelayerAccount, zkEmailProver } = this.deps;\n\n if (!zkEmailProver || !zkEmailProver.baseUrl) {\n this.logger.warn('[email-recovery] zk-email missing prover configuration', { accountId });\n return {\n success: false,\n error: 'zkemail_prover_not_configured',\n message: 'zk-email prover configuration is missing',\n };\n }\n\n const prepared = prepareZkEmailRecovery(emailBlob, accountId);\n if (!prepared.ok) {\n const log = {\n accountId,\n requestId: prepared.requestId,\n proverBaseUrl: zkEmailProver.baseUrl,\n errorCode: prepared.errorCode,\n errorMessage: prepared.message,\n accountIdSubject: prepared.subjectAccountId,\n };\n if (prepared.errorCode === 'zkemail_account_mismatch') {\n this.logger.warn('[email-recovery] zk-email account mismatch', log);\n } else {\n this.logger.warn('[email-recovery] zk-email recovery rejected', log);\n }\n return { success: false, error: prepared.errorCode, message: prepared.message };\n }\n\n const { payload, bindings } = prepared.prepared;\n\n try {\n await ensureSignerAndRelayerAccount();\n } catch (e: any) {\n const msg = e?.message || 'Failed to initialize relayer account';\n this.logger.error('[email-recovery] zk-email ensureSignerAndRelayerAccount failed', {\n accountId,\n requestId: bindings.requestId,\n error: msg,\n });\n return { success: false, error: 'zkemail_relayer_init_failed', message: msg };\n }\n\n\t try {\n\t const proverClient = this.getZkEmailProverClient(zkEmailProver);\n\t const proofResult = await generateZkEmailProofFromPayload(payload, proverClient);\n\n\t\t const contractArgs = {\n\t\t proof: proofResult.proof,\n\t\t public_inputs: proofResult.publicInputs,\n\t\t account_id: bindings.accountId,\n\t\t new_public_key: bindings.newPublicKey,\n\t\t request_id: bindings.requestId,\n\t\t from_email: bindings.fromEmail,\n\t\t timestamp: bindings.timestamp,\n\t\t };\n\n\t const { actions, receiverId } = await buildZkEmailRecoveryActions(this.deps, {\n\t accountId,\n\t contractArgs,\n\t });\n\n\t return sendEmailRecoveryTransaction(this.deps, {\n\t receiverId,\n\t actions,\n\t label: `ZK-email recovery requested for ${accountId}`,\n\t });\n\t\t } catch (error: any) {\n const mapped = mapZkEmailRecoveryError(error);\n\n\t\t this.logger.error('[email-recovery] zk-email recovery error', {\n\t\t accountId,\n\t requestId: bindings.requestId,\n\t\t errorCode: mapped.errorCode,\n\t\t errorMessage: mapped.message,\n\t proverBaseUrl: zkEmailProver.baseUrl,\n\t proverCauseCode: mapped.proverCauseCode,\n\t proverCauseMessage: mapped.proverCauseMessage,\n\t\t });\n\n\t return {\n\t success: false,\n\t error: mapped.errorCode,\n\t message: mapped.message,\n\t };\n\t }\n\t }\n\n}\n"],"mappings":"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"}
|
|
@@ -4,6 +4,18 @@ import { parseHeaderValue, parseRecoverSubjectBindings } from "./emailParsers.js
|
|
|
4
4
|
import { hashRecoveryEmailForAccount } from "./emailEncryptor.js";
|
|
5
5
|
|
|
6
6
|
//#region src/server/email-recovery/rpcCalls.ts
|
|
7
|
+
function normalizeSingleLine(input) {
|
|
8
|
+
return String(input || "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
|
|
9
|
+
}
|
|
10
|
+
function formatEmailRecoveryTxError(error, receiverId) {
|
|
11
|
+
const kind = typeof error?.kind === "string" ? String(error.kind) : "";
|
|
12
|
+
const short = typeof error?.short === "string" ? String(error.short) : "";
|
|
13
|
+
const msg = normalizeSingleLine(error?.message || String(error || ""));
|
|
14
|
+
if (kind === "AccountDoesNotExist" || /AccountDoesNotExist/i.test(short) || /AccountDoesNotExist/i.test(msg) || /account does not exist/i.test(msg)) return `Account "${receiverId}" does not exist`;
|
|
15
|
+
if (/Invalid(Account|Receiver)Id/i.test(kind) || /Invalid(Account|Receiver)Id/i.test(short) || /Invalid(Account|Receiver)Id/i.test(msg)) return `Invalid NEAR account ID "${receiverId}"`;
|
|
16
|
+
if (short && short !== "TxExecutionError" && short !== "RPC error") return `Transaction failed (${short})`;
|
|
17
|
+
return msg || "Unknown email recovery error";
|
|
18
|
+
}
|
|
7
19
|
async function getOutlayerEncryptionPublicKey(deps) {
|
|
8
20
|
const { nearClient, emailDkimVerifierContract } = deps;
|
|
9
21
|
const result = await nearClient.view({
|
|
@@ -48,7 +60,8 @@ async function buildEncryptedEmailRecoveryActions(deps, input) {
|
|
|
48
60
|
encrypted_email_blob: envelope,
|
|
49
61
|
aead_context: aeadContext,
|
|
50
62
|
expected_hashed_email: expectedHashedEmail,
|
|
51
|
-
expected_new_public_key: bindings.newPublicKey
|
|
63
|
+
expected_new_public_key: bindings.newPublicKey,
|
|
64
|
+
request_id: bindings.requestId
|
|
52
65
|
};
|
|
53
66
|
const actions = [{
|
|
54
67
|
action_type: ActionType.FunctionCall,
|
|
@@ -80,10 +93,16 @@ async function buildZkEmailRecoveryActions(deps, input) {
|
|
|
80
93
|
}
|
|
81
94
|
async function buildOnchainEmailRecoveryActions(_deps, input) {
|
|
82
95
|
const { accountId, emailBlob } = input;
|
|
96
|
+
const bindings = parseRecoverSubjectBindings(emailBlob);
|
|
97
|
+
if (!bindings) throw new Error("On-chain email recovery requires Subject: recover-<request_id> <accountId> ed25519:<new_public_key>");
|
|
98
|
+
if (bindings.accountId !== accountId) throw new Error(`On-chain email recovery subject accountId mismatch (expected "${accountId}", got "${bindings.accountId}")`);
|
|
83
99
|
const actions = [{
|
|
84
100
|
action_type: ActionType.FunctionCall,
|
|
85
101
|
method_name: "verify_email_onchain_and_recover",
|
|
86
|
-
args: JSON.stringify({
|
|
102
|
+
args: JSON.stringify({
|
|
103
|
+
email_blob: emailBlob,
|
|
104
|
+
request_id: bindings.requestId
|
|
105
|
+
}),
|
|
87
106
|
gas: "300000000000000",
|
|
88
107
|
deposit: "10000000000000000000000"
|
|
89
108
|
}];
|
|
@@ -121,7 +140,7 @@ async function sendEmailRecoveryTransaction(deps, args) {
|
|
|
121
140
|
message: label
|
|
122
141
|
};
|
|
123
142
|
} catch (error) {
|
|
124
|
-
const msg = error
|
|
143
|
+
const msg = formatEmailRecoveryTxError(error, receiverId);
|
|
125
144
|
return {
|
|
126
145
|
success: false,
|
|
127
146
|
error: msg,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpcCalls.js","names":["bytes: Uint8Array","aeadContext: EmailEncryptionContext","actions: ActionArgsWasm[]","error: any"],"sources":["../../../../src/server/email-recovery/rpcCalls.ts"],"sourcesContent":["import type { ActionArgsWasm } from '../../core/types/actions';\nimport { ActionType, validateActionArgsWasm } from '../../core/types/actions';\nimport { parseContractExecutionError } from '../core/errors';\nimport { hashRecoveryEmailForAccount, type EmailEncryptionContext } from './emailEncryptor';\nimport { parseHeaderValue, parseRecoverSubjectBindings } from './emailParsers';\nimport type { EmailRecoveryResult, EmailRecoveryServiceDeps, EmailRecoveryRequest } from './types';\n\nexport async function getOutlayerEncryptionPublicKey(\n deps: Pick<EmailRecoveryServiceDeps, 'nearClient' | 'emailDkimVerifierContract'>,\n): Promise<Uint8Array> {\n const { nearClient, emailDkimVerifierContract } = deps;\n\n const result = await nearClient.view<{}, unknown>({\n account: emailDkimVerifierContract,\n method: 'get_outlayer_encryption_public_key',\n args: {},\n });\n\n if (typeof result !== 'string' || !result) {\n throw new Error('Outlayer encryption public key is not configured on EmailDkimVerifier');\n }\n\n let bytes: Uint8Array;\n try {\n const decoded = typeof Buffer !== 'undefined'\n ? Buffer.from(result, 'base64')\n : Uint8Array.from(atob(result), c => c.charCodeAt(0));\n bytes = decoded instanceof Uint8Array ? decoded : new Uint8Array(decoded);\n } catch (e) {\n throw new Error(`Failed to decode Outlayer email DKIM public key: ${(e as Error).message}`);\n }\n\n if (bytes.length !== 32) {\n throw new Error(`Outlayer email DKIM public key must be 32 bytes, got ${bytes.length}`);\n }\n\n return bytes;\n}\n\nexport async function buildEncryptedEmailRecoveryActions(\n deps: EmailRecoveryServiceDeps,\n input: {\n accountId: string;\n emailBlob: string;\n recipientPk: Uint8Array;\n encrypt: (args: {\n emailRaw: string;\n aeadContext: EmailEncryptionContext;\n recipientPk: Uint8Array;\n }) => Promise<{ envelope: { version: number; ephemeral_pub: string; nonce: string; ciphertext: string } }>;\n },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const {\n relayerAccountId,\n networkId,\n } = deps;\n const { accountId, emailBlob, recipientPk, encrypt } = input;\n\n const aeadContext: EmailEncryptionContext = {\n account_id: accountId,\n network_id: networkId,\n payer_account_id: relayerAccountId,\n };\n\n const { envelope } = await encrypt({\n emailRaw: emailBlob,\n aeadContext,\n recipientPk,\n });\n\n const bindings = parseRecoverSubjectBindings(emailBlob);\n if (!bindings) {\n throw new Error('Encrypted email recovery requires Subject: recover-<request_id> <accountId> ed25519:<new_public_key>');\n }\n if (bindings.accountId !== accountId) {\n throw new Error(`Encrypted email recovery subject accountId mismatch (expected \"${accountId}\", got \"${bindings.accountId}\")`);\n }\n\n const fromHeader = parseHeaderValue(emailBlob, 'from');\n if (!fromHeader) {\n throw new Error('Encrypted email recovery requires a From: header');\n }\n const expectedHashedEmail = hashRecoveryEmailForAccount({ recoveryEmail: fromHeader, accountId });\n\n const contractArgs = {\n encrypted_email_blob: envelope,\n aead_context: aeadContext,\n expected_hashed_email: expectedHashedEmail,\n expected_new_public_key: bindings.newPublicKey,\n };\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_encrypted_email_and_recover',\n args: JSON.stringify(contractArgs),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function buildZkEmailRecoveryActions(\n deps: EmailRecoveryServiceDeps,\n input: {\n accountId: string;\n contractArgs: {\n proof: unknown;\n public_inputs: string[];\n account_id: string;\n new_public_key: string;\n from_email: string;\n timestamp: string;\n };\n },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const { accountId, contractArgs } = input;\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_zkemail_and_recover',\n args: JSON.stringify(contractArgs),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function buildOnchainEmailRecoveryActions(\n _deps: EmailRecoveryServiceDeps,\n input: { accountId: string; emailBlob: string },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const { accountId, emailBlob } = input;\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_email_onchain_and_recover',\n args: JSON.stringify({\n email_blob: emailBlob,\n }),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function sendEmailRecoveryTransaction(\n deps: EmailRecoveryServiceDeps,\n args: {\n receiverId: string;\n actions: ActionArgsWasm[];\n label: string;\n },\n): Promise<EmailRecoveryResult> {\n const {\n relayerAccountId,\n relayerPrivateKey,\n nearClient,\n queueTransaction,\n fetchTxContext,\n signWithPrivateKey,\n getRelayerPublicKey,\n } = deps;\n\n const { receiverId, actions, label } = args;\n\n return queueTransaction(async () => {\n try {\n const relayerPublicKey = getRelayerPublicKey();\n const { nextNonce, blockHash } = await fetchTxContext(relayerAccountId, relayerPublicKey);\n\n const signed = await signWithPrivateKey({\n nearPrivateKey: relayerPrivateKey,\n signerAccountId: relayerAccountId,\n receiverId,\n nonce: nextNonce,\n blockHash,\n actions,\n });\n\n const result = await nearClient.sendTransaction(signed);\n\n const contractError = parseContractExecutionError(result, receiverId);\n if (contractError) {\n return {\n success: false,\n error: contractError,\n message: contractError,\n };\n }\n\n return {\n success: true,\n transactionHash: result.transaction.hash,\n message: label,\n };\n } catch (error: any) {\n const msg = error?.message || 'Unknown email recovery error';\n return {\n success: false,\n error: msg,\n message: msg,\n };\n }\n }, args.label);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rpcCalls.js","names":["bytes: Uint8Array","aeadContext: EmailEncryptionContext","actions: ActionArgsWasm[]","error: any"],"sources":["../../../../src/server/email-recovery/rpcCalls.ts"],"sourcesContent":["import type { ActionArgsWasm } from '../../core/types/actions';\nimport { ActionType, validateActionArgsWasm } from '../../core/types/actions';\nimport { parseContractExecutionError } from '../core/errors';\nimport { hashRecoveryEmailForAccount, type EmailEncryptionContext } from './emailEncryptor';\nimport { parseHeaderValue, parseRecoverSubjectBindings } from './emailParsers';\nimport type { EmailRecoveryResult, EmailRecoveryServiceDeps, EmailRecoveryRequest } from './types';\n\nfunction normalizeSingleLine(input: string): string {\n return String(input || '')\n .replace(/[\\r\\n]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction formatEmailRecoveryTxError(error: unknown, receiverId: string): string {\n const kind = typeof (error as any)?.kind === 'string' ? String((error as any).kind) : '';\n const short = typeof (error as any)?.short === 'string' ? String((error as any).short) : '';\n const msg = normalizeSingleLine((error as any)?.message || String(error || ''));\n\n // Non-existent target account (common when the Subject includes a typo / unknown account).\n if (\n kind === 'AccountDoesNotExist' ||\n /AccountDoesNotExist/i.test(short) ||\n /AccountDoesNotExist/i.test(msg) ||\n /account does not exist/i.test(msg)\n ) {\n return `Account \"${receiverId}\" does not exist`;\n }\n\n // Invalid / malformed account id.\n if (\n /Invalid(Account|Receiver)Id/i.test(kind) ||\n /Invalid(Account|Receiver)Id/i.test(short) ||\n /Invalid(Account|Receiver)Id/i.test(msg)\n ) {\n return `Invalid NEAR account ID \"${receiverId}\"`;\n }\n\n // Prefer concise NearRpcError \"short\" where available.\n if (short && short !== 'TxExecutionError' && short !== 'RPC error') {\n return `Transaction failed (${short})`;\n }\n\n return msg || 'Unknown email recovery error';\n}\n\nexport async function getOutlayerEncryptionPublicKey(\n deps: Pick<EmailRecoveryServiceDeps, 'nearClient' | 'emailDkimVerifierContract'>,\n): Promise<Uint8Array> {\n const { nearClient, emailDkimVerifierContract } = deps;\n\n const result = await nearClient.view<{}, unknown>({\n account: emailDkimVerifierContract,\n method: 'get_outlayer_encryption_public_key',\n args: {},\n });\n\n if (typeof result !== 'string' || !result) {\n throw new Error('Outlayer encryption public key is not configured on EmailDkimVerifier');\n }\n\n let bytes: Uint8Array;\n try {\n const decoded = typeof Buffer !== 'undefined'\n ? Buffer.from(result, 'base64')\n : Uint8Array.from(atob(result), c => c.charCodeAt(0));\n bytes = decoded instanceof Uint8Array ? decoded : new Uint8Array(decoded);\n } catch (e) {\n throw new Error(`Failed to decode Outlayer email DKIM public key: ${(e as Error).message}`);\n }\n\n if (bytes.length !== 32) {\n throw new Error(`Outlayer email DKIM public key must be 32 bytes, got ${bytes.length}`);\n }\n\n return bytes;\n}\n\nexport async function buildEncryptedEmailRecoveryActions(\n deps: EmailRecoveryServiceDeps,\n input: {\n accountId: string;\n emailBlob: string;\n recipientPk: Uint8Array;\n encrypt: (args: {\n emailRaw: string;\n aeadContext: EmailEncryptionContext;\n recipientPk: Uint8Array;\n }) => Promise<{ envelope: { version: number; ephemeral_pub: string; nonce: string; ciphertext: string } }>;\n },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const {\n relayerAccountId,\n networkId,\n } = deps;\n const { accountId, emailBlob, recipientPk, encrypt } = input;\n\n const aeadContext: EmailEncryptionContext = {\n account_id: accountId,\n network_id: networkId,\n payer_account_id: relayerAccountId,\n };\n\n const { envelope } = await encrypt({\n emailRaw: emailBlob,\n aeadContext,\n recipientPk,\n });\n\n const bindings = parseRecoverSubjectBindings(emailBlob);\n if (!bindings) {\n throw new Error('Encrypted email recovery requires Subject: recover-<request_id> <accountId> ed25519:<new_public_key>');\n }\n if (bindings.accountId !== accountId) {\n throw new Error(`Encrypted email recovery subject accountId mismatch (expected \"${accountId}\", got \"${bindings.accountId}\")`);\n }\n\n const fromHeader = parseHeaderValue(emailBlob, 'from');\n if (!fromHeader) {\n throw new Error('Encrypted email recovery requires a From: header');\n }\n const expectedHashedEmail = hashRecoveryEmailForAccount({ recoveryEmail: fromHeader, accountId });\n\n const contractArgs = {\n encrypted_email_blob: envelope,\n aead_context: aeadContext,\n expected_hashed_email: expectedHashedEmail,\n expected_new_public_key: bindings.newPublicKey,\n request_id: bindings.requestId,\n };\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_encrypted_email_and_recover',\n args: JSON.stringify(contractArgs),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function buildZkEmailRecoveryActions(\n deps: EmailRecoveryServiceDeps,\n input: {\n accountId: string;\n contractArgs: {\n proof: unknown;\n public_inputs: string[];\n account_id: string;\n new_public_key: string;\n request_id: string;\n from_email: string;\n timestamp: string;\n };\n },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const { accountId, contractArgs } = input;\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_zkemail_and_recover',\n args: JSON.stringify(contractArgs),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function buildOnchainEmailRecoveryActions(\n _deps: EmailRecoveryServiceDeps,\n input: { accountId: string; emailBlob: string },\n): Promise<{ actions: ActionArgsWasm[]; receiverId: string }> {\n const { accountId, emailBlob } = input;\n\n const bindings = parseRecoverSubjectBindings(emailBlob);\n if (!bindings) {\n throw new Error('On-chain email recovery requires Subject: recover-<request_id> <accountId> ed25519:<new_public_key>');\n }\n if (bindings.accountId !== accountId) {\n throw new Error(`On-chain email recovery subject accountId mismatch (expected \"${accountId}\", got \"${bindings.accountId}\")`);\n }\n\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.FunctionCall,\n method_name: 'verify_email_onchain_and_recover',\n args: JSON.stringify({\n email_blob: emailBlob,\n request_id: bindings.requestId,\n }),\n gas: '300000000000000',\n deposit: '10000000000000000000000',\n },\n ];\n actions.forEach(validateActionArgsWasm);\n\n return {\n actions,\n receiverId: accountId,\n };\n}\n\nexport async function sendEmailRecoveryTransaction(\n deps: EmailRecoveryServiceDeps,\n args: {\n receiverId: string;\n actions: ActionArgsWasm[];\n label: string;\n },\n): Promise<EmailRecoveryResult> {\n const {\n relayerAccountId,\n relayerPrivateKey,\n nearClient,\n queueTransaction,\n fetchTxContext,\n signWithPrivateKey,\n getRelayerPublicKey,\n } = deps;\n\n const { receiverId, actions, label } = args;\n\n return queueTransaction(async () => {\n try {\n const relayerPublicKey = getRelayerPublicKey();\n const { nextNonce, blockHash } = await fetchTxContext(relayerAccountId, relayerPublicKey);\n\n const signed = await signWithPrivateKey({\n nearPrivateKey: relayerPrivateKey,\n signerAccountId: relayerAccountId,\n receiverId,\n nonce: nextNonce,\n blockHash,\n actions,\n });\n\n const result = await nearClient.sendTransaction(signed);\n\n const contractError = parseContractExecutionError(result, receiverId);\n if (contractError) {\n return {\n success: false,\n error: contractError,\n message: contractError,\n };\n }\n\n return {\n success: true,\n transactionHash: result.transaction.hash,\n message: label,\n };\n } catch (error: any) {\n const msg = formatEmailRecoveryTxError(error, receiverId);\n return {\n success: false,\n error: msg,\n message: msg,\n };\n }\n }, args.label);\n}\n"],"mappings":"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"}
|
|
@@ -334,6 +334,9 @@ function normalizeEmailAddress(input) {
|
|
|
334
334
|
if (angleStart !== -1 && angleEnd > angleStart) return trimmed.slice(angleStart + 1, angleEnd).trim().toLowerCase();
|
|
335
335
|
return trimmed.toLowerCase();
|
|
336
336
|
}
|
|
337
|
+
function normalizeRejectReason(input) {
|
|
338
|
+
return String(input || "").replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
|
|
339
|
+
}
|
|
337
340
|
function toLowercaseHeaderRecord(input) {
|
|
338
341
|
const out = {};
|
|
339
342
|
if (!input) return out;
|
|
@@ -409,7 +412,7 @@ function createCloudflareEmailHandler(service, opts = {}) {
|
|
|
409
412
|
}
|
|
410
413
|
if (!service.emailRecovery) {
|
|
411
414
|
logger.warn("[email] rejecting: EmailRecoveryService not configured");
|
|
412
|
-
message.setReject("
|
|
415
|
+
message.setReject("Email recovery relayer rejected email: email recovery service unavailable");
|
|
413
416
|
return;
|
|
414
417
|
}
|
|
415
418
|
const result = await service.emailRecovery.requestEmailRecovery({
|
|
@@ -420,9 +423,11 @@ function createCloudflareEmailHandler(service, opts = {}) {
|
|
|
420
423
|
if (!result?.success) {
|
|
421
424
|
logger.warn("[email] recovery failed", {
|
|
422
425
|
accountId: parsed.accountId,
|
|
423
|
-
error: result?.error || "unknown"
|
|
426
|
+
error: result?.error || "unknown",
|
|
427
|
+
message: result?.message
|
|
424
428
|
});
|
|
425
|
-
|
|
429
|
+
const reason = normalizeRejectReason(result?.message || result?.error || "recovery failed");
|
|
430
|
+
message.setReject(`Email recovery relayer rejected email: ${reason}`);
|
|
426
431
|
return;
|
|
427
432
|
}
|
|
428
433
|
logger.info("[email] recovery submitted", { accountId: parsed.accountId });
|