@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"express.js","names":["out: ShamirApplyServerLockResponse","e: any","out: ShamirRemoveServerLockResponse","normalizedHeaders: Record<string, string>","v","base: Logger","allowedOrigin: string | '*' | undefined","error: any","e: any","result","currentKeyId: string | null","shamirReady: boolean | null","shamirCurrentKeyId: string | null","shamirError: string | undefined","timer: any"],"sources":["../../../../src/server/core/SessionService.ts","../../../../src/core/WalletIframe/validation.ts","../../../../src/server/core/shamirHandlers.ts","../../../../src/server/email-recovery/zkEmail/index.ts","../../../../src/server/email-recovery/emailParsers.ts","../../../../src/server/core/logger.ts","../../../../src/server/router/logger.ts","../../../../src/server/router/express-adaptor.ts"],"sourcesContent":["\nexport interface SessionConfig {\n jwt?: {\n /** Required: JWT signing hook; return a complete token */\n signToken?: (input: { header: Record<string, unknown>; payload: Record<string, unknown> }) => Promise<string> | string;\n /** Required: JWT verification hook */\n verifyToken?: (token: string) => Promise<{ valid: boolean; payload?: any }> | { valid: boolean; payload?: any };\n /** Optional: sliding refresh window (seconds) to allow /session/refresh before exp, default 900 (15 min) */\n refreshWindowSec?: number;\n /** Optional: build additional claims to include in the payload */\n buildClaims?: (input: { sub: string; context?: Record<string, unknown> }) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;\n };\n cookie?: {\n /** Cookie name. Default: 'w3a_session' */\n name?: string;\n /** Optional override: build Set-Cookie header for a new token */\n buildSetHeader?: (token: string) => string;\n /** Optional override: build Set-Cookie header that clears the cookie */\n buildClearHeader?: () => string;\n /** Optional override: extract token from headers (Authorization/Cookie) */\n extractToken?: (headers: Record<string, string | string[] | undefined>, cookieName: string) => string | null;\n };\n}\n\nexport class SessionService<TClaims = any> {\n private cfg: NonNullable<SessionConfig>;\n\n constructor(cfg: NonNullable<SessionConfig>) {\n this.cfg = cfg || ({} as any);\n }\n\n getCookieName(): string {\n return this.cfg?.cookie?.name || 'w3a_session';\n }\n\n buildSetCookie(token: string): string {\n if (this.cfg?.cookie?.buildSetHeader) return this.cfg.cookie.buildSetHeader(token);\n const name = this.getCookieName();\n const cookieParts = [`${name}=${token}`];\n const path = '/';\n const httpOnly = true;\n const secure = true; // default secure\n const sameSite = 'Lax';\n const maxAge = 24 * 3600; // 1 day default\n cookieParts.push(`Path=${path}`);\n if (httpOnly) cookieParts.push('HttpOnly');\n if (secure) cookieParts.push('Secure');\n if (sameSite) cookieParts.push(`SameSite=${sameSite}`);\n if (maxAge) {\n cookieParts.push(`Max-Age=${maxAge}`);\n const expires = new Date(Date.now() + (maxAge * 1000)).toUTCString();\n cookieParts.push(`Expires=${expires}`);\n }\n return cookieParts.join('; ');\n }\n\n buildClearCookie(): string {\n if (this.cfg?.cookie?.buildClearHeader) return this.cfg.cookie.buildClearHeader();\n const name = this.getCookieName();\n const path = '/';\n const secure = true;\n const httpOnly = true;\n const parts = [\n `${name}=`,\n `Path=${path}`,\n 'Max-Age=0',\n 'Expires=Thu, 01 Jan 1970 00:00:00 GMT'\n ];\n if (httpOnly) parts.push('HttpOnly');\n if (secure) parts.push('Secure');\n const sameSite = 'Lax';\n if (sameSite) parts.push(`SameSite=${sameSite}`);\n return parts.join('; ');\n }\n\n /** Sign a JWT with configured algorithm. Adds iat/exp and copies iss/aud. */\n async signJwt(sub: string, extraClaims?: Record<string, unknown>): Promise<string> {\n const jwt = this.cfg?.jwt || {};\n const built = await Promise.resolve(jwt.buildClaims?.({ sub, context: extraClaims })) || {};\n const payload = { sub, ...(extraClaims || {}), ...(built || {}) } as Record<string, unknown>;\n if (typeof jwt.signToken === 'function') {\n // Full override of signing: user supplies the complete token\n const token = await Promise.resolve(jwt.signToken({ header: { typ: 'JWT' }, payload } as any));\n return token;\n }\n throw new Error('SessionService: No JWT signing hook or provider configured');\n }\n\n /** Verify signature and expiration. Returns payload on success. */\n async verifyJwt(token: string): Promise<{ valid: boolean; payload?: any }> {\n const verify = this.cfg?.jwt?.verifyToken;\n if (typeof verify !== 'function') return { valid: false };\n return await Promise.resolve(verify(token));\n }\n\n parse(headers: Record<string, string | string[] | undefined>): Promise<{ ok: boolean; claims?: TClaims } | { ok: false }>{\n const authHeader = (headers['authorization'] || headers['Authorization']) as string | undefined;\n let token: string | null = null;\n if (authHeader && /^Bearer\\s+/.test(authHeader)) token = authHeader.replace(/^Bearer\\s+/i, '').trim();\n const cookieHeader = (headers['cookie'] || headers['Cookie']) as string | undefined;\n if (!token && cookieHeader) {\n const name = this.getCookieName();\n for (const part of cookieHeader.split(';')) {\n const [k, v] = part.split('=');\n if (k && k.trim() === name) { token = (v || '').trim(); break; }\n }\n }\n if (!token) return Promise.resolve({ ok: false });\n return this.verifyJwt(token).then(v => v.valid ? { ok: true, claims: v.payload as TClaims } : { ok: false });\n }\n\n // === token helpers ===\n extractTokenFromHeaders(headers: Record<string, string | string[] | undefined>): string | null {\n if (this.cfg?.cookie?.extractToken) return this.cfg.cookie.extractToken(headers, this.getCookieName());\n const authHeader = (headers['authorization'] || headers['Authorization']) as string | undefined;\n if (authHeader && /^Bearer\\s+/.test(authHeader)) return authHeader.replace(/^Bearer\\s+/i, '').trim();\n const cookieHeader = (headers['cookie'] || headers['Cookie']) as string | undefined;\n if (cookieHeader) {\n const name = this.getCookieName();\n for (const part of cookieHeader.split(';')) {\n const [k, v] = part.split('=');\n if (k && k.trim() === name) return (v || '').trim();\n }\n }\n return null;\n }\n\n async refresh(headers: Record<string, string | string[] | undefined>): Promise<{ ok: boolean; jwt?: string; code?: string; message?: string }>{\n try {\n const token = this.extractTokenFromHeaders(headers);\n if (!token) return { ok: false, code: 'unauthorized', message: 'No session token' };\n const v = await this.verifyJwt(token);\n if (!v.valid) return { ok: false, code: 'unauthorized', message: 'Invalid token' };\n const payload: any = v.payload || {};\n if (!this.isWithinRefreshWindow(payload)) return { ok: false, code: 'not_eligible', message: 'Not within refresh window' };\n const sub = String(payload.sub || '');\n if (!sub) return { ok: false, code: 'invalid_claims', message: 'Missing sub claim' };\n const next = await this.signJwt(sub);\n return { ok: true, jwt: next };\n } catch (e: any) {\n return { ok: false, code: 'internal', message: e?.message || 'Refresh failed' };\n }\n }\n\n nowSeconds(): number { return Math.floor(Date.now() / 1000); }\n\n private isWithinRefreshWindow(payload: any): boolean {\n try {\n const now = this.nowSeconds();\n const exp = Number(payload?.exp || 0);\n if (!exp || now >= exp) return false; // no refresh if already expired\n const windowSec = Number(this.cfg?.jwt?.refreshWindowSec || 15 * 60);\n return (exp - now) <= windowSec;\n } catch { return false; }\n }\n}\n\n/*\n * Utility: parse comma-separated list of origins into a normalized unique list\n * - canonicalizes to protocol + host + optional port\n * - lowercases host, strips path/query/hash, trims spaces/trailing slashes\n */\nexport function parseCsvList(input?: string): string[] {\n const out = new Set<string>();\n for (const raw of String(input || '').split(',')) {\n const s = raw.trim();\n if (!s) continue;\n try {\n const u = new URL(s);\n const host = u.hostname.toLowerCase();\n const port = u.port ? `:${u.port}` : '';\n const proto = u.protocol === 'http:' || u.protocol === 'https:' ? u.protocol : 'https:';\n out.add(`${proto}//${host}${port}`);\n } catch {\n const stripped = s.replace(/\\/$/, '');\n if (stripped) out.add(stripped);\n }\n }\n return Array.from(out);\n}\n\n/*\n * Utility: merge multiple CSV lists of origins and return normalized list or '*'\n */\nexport function buildCorsOrigins(...inputs: Array<string | undefined>): string[] | '*' {\n const merged = new Set<string>();\n for (const input of inputs) {\n for (const origin of parseCsvList(input)) merged.add(origin);\n }\n const list = Array.from(merged);\n return list.length > 0 ? list : '*';\n}\n","// Shared runtime validation helpers for Wallet Iframe code.\n\nexport function isObject(x: unknown): x is Record<string, unknown> {\n return x !== null && typeof x === 'object';\n}\n\nexport function isString(x: unknown): x is string {\n return typeof x === 'string';\n}\n\nexport function isNonEmptyString(x: unknown): x is string {\n return typeof x === 'string' && x.length > 0;\n}\n\nexport function isNumber(x: unknown): x is number {\n return typeof x === 'number';\n}\n\nexport function isFiniteNumber(x: unknown): x is number {\n return typeof x === 'number' && Number.isFinite(x);\n}\n\nexport function isFunction(x: unknown): x is Function {\n return typeof x === 'function';\n}\n\nexport function isBoolean(x: unknown): x is boolean {\n return typeof x === 'boolean';\n}\n\nexport function isArray<T = unknown>(x: unknown): x is T[] {\n return Array.isArray(x);\n}\n\n// ========= Assertions (throw on mismatch) =========\n\nexport function assertString(val: unknown, name = 'value'): string {\n if (typeof val !== 'string') throw new Error(`Invalid ${name}: expected string`);\n return val;\n}\n\nexport function assertNumber(val: unknown, name = 'value'): number {\n if (typeof val !== 'number' || !Number.isFinite(val)) throw new Error(`Invalid ${name}: expected finite number`);\n return val;\n}\n\nexport function assertBoolean(val: unknown, name = 'value'): boolean {\n if (typeof val !== 'boolean') throw new Error(`Invalid ${name}: expected boolean`);\n return val;\n}\n\nexport function assertObject<T extends Record<string, unknown> = Record<string, unknown>>(val: unknown, name = 'value'): T {\n if (!isObject(val)) throw new Error(`Invalid ${name}: expected object`);\n return val as T;\n}\n\nexport function assertArray<T = unknown>(val: unknown, name = 'value'): T[] {\n if (!Array.isArray(val)) throw new Error(`Invalid ${name}: expected array`);\n return val as T[];\n}\n\n// Shallowly remove function-valued properties (postMessage/clone safety)\nexport function stripFunctionsShallow<T extends Record<string, unknown>>(obj?: T): Partial<T> | undefined {\n if (!obj || !isObject(obj)) return undefined;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (!isFunction(v)) out[k] = v as unknown;\n }\n return out as Partial<T>;\n}\n\n// ===============================\n// SignedTransaction shape helpers\n// ===============================\n\nexport interface PlainSignedTransactionLike {\n transaction: unknown;\n signature: unknown;\n borsh_bytes?: unknown;\n borshBytes?: unknown;\n base64Encode?: unknown;\n}\n\nexport function isPlainSignedTransactionLike(x: unknown): x is PlainSignedTransactionLike {\n if (!isObject(x)) return false;\n const hasTx = 'transaction' in x;\n const hasSig = 'signature' in x;\n const bytes = x as { borsh_bytes?: unknown; borshBytes?: unknown };\n const hasBytes = Array.isArray(bytes.borsh_bytes) || bytes.borshBytes instanceof Uint8Array;\n const hasMethod = typeof (x as { base64Encode?: unknown }).base64Encode === 'function';\n return hasTx && hasSig && hasBytes && !hasMethod;\n}\n\nexport function extractBorshBytesFromPlainSignedTx(x: PlainSignedTransactionLike): number[] {\n const asArray = Array.isArray(x.borsh_bytes) ? (x.borsh_bytes as number[]) : undefined;\n if (asArray) return asArray;\n const asU8 = (x.borshBytes instanceof Uint8Array) ? x.borshBytes : undefined;\n return Array.from(asU8 || new Uint8Array());\n}\n","import type {\n ShamirApplyServerLockRequest,\n ShamirApplyServerLockResponse,\n ShamirRemoveServerLockRequest,\n ShamirRemoveServerLockResponse,\n} from './types';\nimport type { ShamirService } from './ShamirService';\nimport { isString } from '../../core/WalletIframe/validation';\n\nexport async function handleApplyServerLock(\n service: ShamirService,\n request: { body?: { kek_c_b64u?: string } },\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n const kek_c_b64u = request.body?.kek_c_b64u;\n if (!isString(kek_c_b64u) || !kek_c_b64u) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'kek_c_b64u required and must be a non-empty string' }),\n };\n }\n\n const out: ShamirApplyServerLockResponse = await service.applyServerLock(kek_c_b64u);\n const keyId = service.getCurrentShamirKeyId();\n\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ ...out, keyId }),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n\nexport async function handleRemoveServerLock(\n service: ShamirService,\n request: { body?: { kek_cs_b64u?: string; keyId?: string } },\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n const body = request.body;\n if (!body) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'Missing body' }),\n };\n }\n const { kek_cs_b64u, keyId } = body;\n if (!isString(kek_cs_b64u) || !kek_cs_b64u) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'kek_cs_b64u required and must be a non-empty string' }),\n };\n }\n if (!isString(keyId) || !keyId) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'keyId required and must be a non-empty string' }),\n };\n }\n\n const providedKeyId = String(keyId);\n const currentKeyId = service.getCurrentShamirKeyId();\n let out: ShamirRemoveServerLockResponse;\n\n if (currentKeyId && providedKeyId === currentKeyId) {\n out = await service.removeServerLock(kek_cs_b64u);\n } else if (service.hasGraceKey(providedKeyId)) {\n out = await service.removeGraceServerLockWithKey(providedKeyId, { kek_cs_b64u } as ShamirRemoveServerLockRequest);\n } else {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'unknown keyId' }),\n };\n }\n\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(out),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n\nexport async function handleGetShamirKeyInfo(\n service: ShamirService,\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n await service.ensureReady();\n const currentKeyId = service.getCurrentShamirKeyId();\n const graceKeyIds = service.getGraceKeyIds();\n\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n currentKeyId,\n p_b64u: service.getShamirConfig()?.shamir_p_b64u ?? null,\n graceKeyIds,\n }),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n\nexport async function handleListGraceKeys(\n service: ShamirService,\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n await service.ensureGraceKeysLoaded();\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ graceKeyIds: service.getGraceKeyIds() }),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n\nexport async function handleAddGraceKey(\n service: ShamirService,\n request: { e_s_b64u?: string; d_s_b64u?: string },\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n const { e_s_b64u, d_s_b64u } = request || ({} as any);\n if (!isString(e_s_b64u) || !e_s_b64u || !isString(d_s_b64u) || !d_s_b64u) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'e_s_b64u and d_s_b64u required' }),\n };\n }\n\n await service.ensureGraceKeysLoaded();\n const added = await service.addGraceKeyInternal({ e_s_b64u, d_s_b64u }, { persist: true, skipIfExists: true });\n if (!added) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'failed to add grace key' }),\n };\n }\n\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ keyId: added.keyId }),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n\nexport async function handleRemoveGraceKey(\n service: ShamirService,\n request: { keyId?: string },\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n const keyId = request?.keyId;\n if (!isString(keyId) || !keyId) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'keyId required and must be a non-empty string' }),\n };\n }\n\n const removed = await service.removeGraceKeyInternal(keyId, { persist: true });\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ removed }),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n","import { ZkEmailProverClient, type ZkEmailProverClientOptions, type ZkEmailProverError } from './proverClient';\n\nexport { ZkEmailProverClient } from './proverClient';\nexport type { ZkEmailProverClientOptions, ZkEmailProverError, ZkEmailProverResponse } from './proverClient';\n\nexport interface ForwardableEmailPayload {\n from: string;\n to: string;\n headers: Record<string, string>;\n raw?: string;\n rawSize?: number;\n}\n\nexport type NormalizedEmailResult =\n | { ok: true; payload: ForwardableEmailPayload }\n | { ok: false; code: string; message: string };\n\nexport interface GenerateZkEmailProofResult {\n proof: unknown;\n publicInputs: string[];\n}\n\nexport interface ParsedZkEmailBindings {\n accountId: string;\n newPublicKey: string;\n fromEmail: string;\n timestamp: string;\n requestId: string;\n}\n\n/**\n * Build a minimal ForwardableEmailPayload from a raw RFC822 email string.\n * This is primarily used by server-side helpers that receive only a raw\n * email blob (no pre-normalized headers).\n */\nexport function buildForwardablePayloadFromRawEmail(raw: string): ForwardableEmailPayload {\n const safeRaw = typeof raw === 'string' ? raw : '';\n const lines = safeRaw.split(/\\r?\\n/);\n\n const getHeader = (name: string): string | undefined => {\n const line = lines.find(l => new RegExp(`^${name}:`, 'i').test(l));\n if (!line) return undefined;\n const idx = line.indexOf(':');\n const rest = idx >= 0 ? line.slice(idx + 1) : '';\n const value = rest.trim();\n return value || undefined;\n };\n\n const fromHeader = getHeader('from') || 'unknown@zkemail.local';\n const toHeader = getHeader('to') || 'recover@zkemail.local';\n\n const headers: Record<string, string> = {};\n const subjectHeader = getHeader('subject');\n const dateHeader = getHeader('date');\n\n if (fromHeader) headers.from = fromHeader;\n if (toHeader) headers.to = toHeader;\n if (subjectHeader) headers.subject = subjectHeader;\n if (dateHeader) headers.date = dateHeader;\n\n return {\n from: fromHeader,\n to: toHeader,\n headers,\n raw: safeRaw,\n rawSize: safeRaw.length,\n };\n}\n\nexport function normalizeForwardableEmailPayload(input: unknown): NormalizedEmailResult {\n if (!input || typeof input !== 'object') {\n return { ok: false, code: 'invalid_email', message: 'JSON body required' };\n }\n\n const body = input as Partial<ForwardableEmailPayload>;\n const { from, to, headers, raw, rawSize } = body;\n\n if (!from || typeof from !== 'string' || !to || typeof to !== 'string') {\n return { ok: false, code: 'invalid_email', message: 'from and to are required' };\n }\n\n if (!headers || typeof headers !== 'object') {\n return { ok: false, code: 'invalid_email', message: 'headers object is required' };\n }\n\n const normalizedHeaders: Record<string, string> = {};\n for (const [k, v] of Object.entries(headers as Record<string, unknown>)) {\n normalizedHeaders[String(k).toLowerCase()] = String(v);\n }\n\n return {\n ok: true,\n payload: {\n from,\n to,\n headers: normalizedHeaders,\n raw: typeof raw === 'string' ? raw : undefined,\n rawSize: typeof rawSize === 'number' ? rawSize : undefined,\n },\n };\n}\n\n/**\n * Parse NEAR accountId from the Subject line inside a raw RFC822 email.\n *\n * Expected format (case-insensitive on \"Subject\" and \"recover\"):\n * Subject: recover-123ABC bob.testnet ed25519:<pk>\n *\n * Returns the parsed accountId (e.g. \"bob.testnet\") or null if not found.\n */\nexport function parseAccountIdFromSubject(raw: string | undefined | null): string | null {\n if (!raw || typeof raw !== 'string') return null;\n\n // Accept either a full RFC822 message (with \"Subject: ...\" header)\n // or a bare Subject value (\"recover-123ABC bob.testnet ed25519:<pk>\").\n let subjectText = '';\n const lines = raw.split(/\\r?\\n/);\n const subjectLine = lines.find(line => /^subject:/i.test(line));\n if (subjectLine) {\n const idx = subjectLine.indexOf(':');\n const restRaw = idx >= 0 ? subjectLine.slice(idx + 1) : '';\n subjectText = restRaw.trim();\n } else {\n subjectText = raw.trim();\n }\n\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: \"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?.[2]) {\n return match[2];\n }\n\n return null;\n}\n\nfunction parseSubjectBindings(\n rawSubject: string | undefined | null\n): { accountId: string; newPublicKey: string; requestId: string } | null {\n if (!rawSubject || typeof rawSubject !== 'string') return null;\n\n const lines = rawSubject.split(/\\r?\\n/);\n let subjectText = '';\n const subjectLine = lines.find(line => /^subject:/i.test(line));\n if (subjectLine) {\n const idx = subjectLine.indexOf(':');\n const restRaw = idx >= 0 ? subjectLine.slice(idx + 1) : '';\n subjectText = restRaw.trim();\n } else {\n subjectText = rawSubject.trim();\n }\n if (!subjectText) return null;\n\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\n return {\n accountId,\n newPublicKey,\n requestId,\n };\n}\n\nexport function extractZkEmailBindingsFromPayload(\n payload: ForwardableEmailPayload\n): ParsedZkEmailBindings | null {\n const raw = payload.raw || '';\n const lines = raw.split(/\\r?\\n/);\n\n const subjectLine = lines.find(line => /^subject:/i.test(line));\n const subjectBindings = parseSubjectBindings(subjectLine ?? '');\n if (!subjectBindings) {\n return null;\n }\n\n const headers = payload.headers || {};\n let fromEmailRaw: string | undefined =\n (headers['from'] as any) ||\n (headers['x-from-email'] as any);\n let dateRaw: string | undefined =\n (headers['date'] as any) ||\n (headers['x-original-date'] as any);\n\n // Fallback: if headers object does not contain from/date,\n // attempt to parse them from the raw RFC822 email lines.\n if (!fromEmailRaw || !dateRaw) {\n for (const line of lines) {\n if (!fromEmailRaw && /^from:/i.test(line)) {\n const idx = line.indexOf(':');\n fromEmailRaw = idx >= 0 ? line.slice(idx + 1).trim() : '';\n }\n if (!dateRaw && /^date:/i.test(line)) {\n const idx = line.indexOf(':');\n dateRaw = idx >= 0 ? line.slice(idx + 1).trim() : '';\n }\n if (fromEmailRaw && dateRaw) break;\n }\n }\n\n const fromEmail = String(fromEmailRaw || '').trim();\n const timestamp = String(dateRaw || '').trim();\n\n if (!fromEmail || !timestamp) {\n return null;\n }\n\n return {\n accountId: subjectBindings.accountId,\n newPublicKey: subjectBindings.newPublicKey,\n fromEmail,\n timestamp,\n requestId: subjectBindings.requestId,\n };\n}\n\nexport async function generateZkEmailProofFromPayload(\n payload: ForwardableEmailPayload,\n prover: ZkEmailProverClientOptions | ZkEmailProverClient\n): Promise<GenerateZkEmailProofResult> {\n if (!payload.raw || typeof payload.raw !== 'string') {\n const err: ZkEmailProverError = Object.assign(\n new Error('raw email contents are required to generate a zk-email proof'),\n { code: 'missing_raw_email' }\n );\n throw err;\n }\n\n const client =\n (prover && typeof (prover as any).proveEmail === 'function')\n ? (prover as ZkEmailProverClient)\n : new ZkEmailProverClient(prover as ZkEmailProverClientOptions);\n\n const res = await client.proveEmail(payload.raw);\n\n return {\n proof: res.proof,\n publicInputs: res.publicSignals,\n };\n}\n","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","export type Logger = {\n debug?: (...args: unknown[]) => void;\n info?: (...args: unknown[]) => void;\n warn?: (...args: unknown[]) => void;\n error?: (...args: unknown[]) => void;\n log?: (...args: unknown[]) => void;\n};\n\nexport type NormalizedLogger = {\n debug: (...args: unknown[]) => void;\n info: (...args: unknown[]) => void;\n warn: (...args: unknown[]) => void;\n error: (...args: unknown[]) => void;\n};\n\nfunction safe(fn?: (...args: unknown[]) => void): (...args: unknown[]) => void {\n if (!fn) return () => {};\n return (...args: unknown[]) => {\n try {\n fn(...args);\n } catch {\n // Never allow logging to break request handling.\n }\n };\n}\n\n/**\n * Library code should never call `console.*` directly; the host decides where logs go.\n * - Default: no logs\n * - To enable: pass `logger: console` (or a structured logger) to the host config.\n */\nexport function normalizeLogger(logger?: Logger | null): NormalizedLogger {\n if (!logger) {\n return { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} };\n }\n\n const base: Logger = logger;\n const log = typeof base.log === 'function' ? base.log.bind(base) : undefined;\n\n const debug = (typeof base.debug === 'function' ? base.debug.bind(base) : log);\n const info = (typeof base.info === 'function' ? base.info.bind(base) : log);\n const warn = (typeof base.warn === 'function' ? base.warn.bind(base) : log);\n const error = (typeof base.error === 'function' ? base.error.bind(base) : log);\n\n return {\n debug: safe(debug),\n info: safe(info),\n warn: safe(warn),\n error: safe(error),\n };\n}\n\n","import type { Logger, NormalizedLogger } from '../core/logger';\nimport { normalizeLogger } from '../core/logger';\n\nexport type RouterLogger = Logger;\nexport type NormalizedRouterLogger = NormalizedLogger;\nexport const normalizeRouterLogger = normalizeLogger;\n","import type { Request, Response, Router as ExpressRouter } from 'express';\nimport express from 'express';\nimport type { AuthService } from '../core/AuthService';\nimport { buildCorsOrigins } from '../core/SessionService';\nimport {\n handleApplyServerLock,\n handleRemoveServerLock,\n handleGetShamirKeyInfo,\n handleListGraceKeys,\n handleAddGraceKey,\n handleRemoveGraceKey,\n} from '../core/shamirHandlers';\nimport { parseRecoverEmailRequest } from '../email-recovery/emailParsers';\nimport type { DelegateActionPolicy } from '../delegateAction';\nimport type { RouterLogger } from './logger';\nimport { normalizeRouterLogger } from './logger';\n\nexport interface RelayRouterOptions {\n healthz?: boolean;\n readyz?: boolean;\n // Optional list(s) of CORS origins (CSV strings or literal origins).\n // Pass raw strings; the router normalizes/merges internally.\n corsOrigins?: Array<string | undefined>;\n /**\n * Optional route for submitting NEP-461 SignedDelegate meta-transactions.\n *\n * - When omitted: disabled.\n * - When set: enabled at `route`.\n *\n * `policy` is server-controlled and is never read from the request body.\n */\n signedDelegate?: {\n route: string;\n policy?: DelegateActionPolicy;\n };\n sessionRoutes?: { auth?: string; logout?: string };\n session?: SessionAdapter | null;\n // Optional logger; defaults to silent.\n logger?: RouterLogger | null;\n}\n\nfunction normalizePath(path: string): string {\n const trimmed = String(path || '').trim();\n if (!trimmed) return '';\n return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;\n}\n\nfunction withCors(res: Response, opts?: RelayRouterOptions, req?: Request): void {\n if (!opts?.corsOrigins) return;\n\n let allowedOrigin: string | '*' | undefined;\n const normalized = buildCorsOrigins(...(opts.corsOrigins || []));\n if (normalized === '*') {\n allowedOrigin = '*';\n res.set('Access-Control-Allow-Origin', '*');\n } else if (Array.isArray(normalized)) {\n const origin = String((req as any)?.headers?.origin || '').trim();\n if (origin && normalized.includes(origin)) {\n allowedOrigin = origin;\n res.set('Access-Control-Allow-Origin', origin);\n res.set('Vary', 'Origin');\n }\n }\n\n res.set('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type,Authorization');\n // Only advertise credentials when we echo back a specific origin (not '*')\n if (allowedOrigin && allowedOrigin !== '*') {\n res.set('Access-Control-Allow-Credentials', 'true');\n }\n}\n\n// Minimal session adapter interface expected by the routers\nexport interface SessionAdapter {\n signJwt(sub: string, extra?: Record<string, unknown>): Promise<string>;\n parse(headers: Record<string, string | string[] | undefined>): Promise<{ ok: boolean; claims?: any } | { ok: false }>;\n buildSetCookie(token: string): string;\n buildClearCookie(): string;\n refresh(headers: Record<string, string | string[] | undefined>): Promise<{ ok: boolean; jwt?: string; code?: string; message?: string }>;\n}\n\nexport function createRelayRouter(service: AuthService, opts: RelayRouterOptions = {}): ExpressRouter {\n const router = express.Router();\n const mePath = opts.sessionRoutes?.auth || '/session/auth';\n const logoutPath = opts.sessionRoutes?.logout || '/session/logout';\n const logger = normalizeRouterLogger(opts.logger);\n const signedDelegatePath = (() => {\n if (!opts.signedDelegate) return '';\n const raw = String(opts.signedDelegate.route || '').trim();\n if (!raw) throw new Error('RelayRouterOptions.signedDelegate.route is required');\n return normalizePath(raw);\n })();\n const signedDelegatePolicy = opts.signedDelegate?.policy;\n\n // Optional CORS: implemented here to keep setup simple for example relayers.\n // If you prefer custom CORS middleware, omit `corsOrigins` and wire your own.\n router.use((req: Request, res: Response, next: any) => {\n withCors(res, opts, req);\n const method = String((req as any)?.method || '').toUpperCase();\n if (opts.corsOrigins && method === 'OPTIONS') {\n res.status(204).send('');\n return;\n }\n next();\n });\n\n router.post(\n '/create_account_and_register_user',\n async (req: any, res: any) => {\n try {\n const {\n new_account_id,\n new_public_key,\n vrf_data,\n webauthn_registration,\n deterministic_vrf_public_key,\n authenticator_options\n } = req.body || ({} as any);\n\n if (!new_account_id || typeof new_account_id !== 'string') throw new Error('Missing or invalid new_account_id');\n if (!new_public_key || typeof new_public_key !== 'string') throw new Error('Missing or invalid new_public_key');\n if (!vrf_data || typeof vrf_data !== 'object') throw new Error('Missing or invalid vrf_data');\n if (!webauthn_registration || typeof webauthn_registration !== 'object') throw new Error('Missing or invalid webauthn_registration');\n\n const result = await service.createAccountAndRegisterUser({\n new_account_id,\n new_public_key,\n vrf_data,\n webauthn_registration,\n deterministic_vrf_public_key,\n authenticator_options\n });\n\n if (result.success) res.status(200).json(result);\n else res.status(400).json(result);\n } catch (error: any) {\n res.status(500).json({ success: false, error: error?.message || 'internal error' });\n }\n }\n );\n\n if (signedDelegatePath) {\n router.options(signedDelegatePath, (_req: any, res: any) => {\n res.sendStatus(204);\n });\n\n router.post(signedDelegatePath, async (req: any, res: any) => {\n try {\n const { hash, signedDelegate } = req.body || {};\n if (typeof hash !== 'string' || !hash || !signedDelegate) {\n res.status(400).json({ ok: false, code: 'invalid_body', message: 'Expected { hash, signedDelegate }' });\n return;\n }\n\n const result = await service.executeSignedDelegate({\n hash,\n signedDelegate,\n policy: signedDelegatePolicy,\n });\n\n if (!result || !result.ok) {\n res.status(400).json({\n ok: false,\n code: result?.code || 'delegate_execution_failed',\n message: result?.error || 'Failed to execute delegate action',\n });\n return;\n }\n\n res.status(200).json({\n ok: true,\n relayerTxHash: result.transactionHash || null,\n status: 'submitted',\n outcome: result.outcome ?? null,\n });\n } catch (e: any) {\n res.status(500).json({\n ok: false,\n code: 'internal',\n message: e?.message || 'Internal error while executing delegate action',\n });\n }\n });\n }\n\n router.post('/vrf/apply-server-lock', async (req: any, res: any) => {\n const shamir = service.shamirService;\n if (!shamir || !shamir.hasShamir()) {\n return res.status(503).json({ error: 'shamir_disabled', message: 'Shamir 3-pass is not configured on this server' });\n }\n try {\n const serverResponse = await handleApplyServerLock(shamir, { body: req.body });\n Object.entries(serverResponse.headers).forEach(([k, v]) => res.set(k, v as any));\n res.status(serverResponse.status);\n res.send(JSON.parse(serverResponse.body));\n } catch (e: any) {\n res.status(500).json({ error: 'internal', details: e?.message });\n }\n });\n\n router.post('/vrf/remove-server-lock', async (req: any, res: any) => {\n const shamir = service.shamirService;\n if (!shamir || !shamir.hasShamir()) {\n return res.status(503).json({ error: 'shamir_disabled', message: 'Shamir 3-pass is not configured on this server' });\n }\n try {\n const serverResponse = await handleRemoveServerLock(shamir, { body: req.body });\n Object.entries(serverResponse.headers).forEach(([k, v]) => res.set(k, v as any));\n res.status(serverResponse.status);\n res.send(JSON.parse(serverResponse.body));\n } catch (e: any) {\n res.status(500).json({ error: 'internal', details: e?.message });\n }\n });\n\n // VRF + WebAuthn session verification (VIEW call) + optional session issuance\n router.post('/verify-authentication-response', async (req: any, res: any) => {\n try {\n if (!req?.body) {\n res.status(400).json({ code: 'invalid_body', message: 'Request body is required' });\n return;\n }\n const body = req.body;\n const valid = body && body.vrf_data && body.webauthn_authentication;\n if (!valid) {\n res.status(400).json({ code: 'invalid_body', message: 'vrf_data and webauthn_authentication are required' });\n return;\n }\n const result = await service.verifyAuthenticationResponse(body);\n const status = result.success ? 200 : 400;\n if (status !== 200) {\n res.status(status).json({ code: 'not_verified', message: result.message || 'Authentication verification failed' });\n return;\n }\n const sessionKind = ((body?.sessionKind || body?.session_kind) === 'cookie') ? 'cookie' : 'jwt';\n const session = opts.session;\n if (session && result.verified) {\n try {\n const sub = String(body.vrf_data.user_id || '');\n const token = await session.signJwt(sub, { rpId: body.vrf_data.rp_id, blockHeight: body.vrf_data.block_height });\n // Best-effort server log without sensitive data\n logger.info(`[relay] creating ${sessionKind === 'cookie' ? 'HttpOnly session' : 'JWT'} for`, sub);\n if (sessionKind === 'cookie') {\n res.set('Set-Cookie', session.buildSetCookie(token));\n const { jwt: _omit, ...rest } = result as any;\n res.status(200).json(rest);\n return;\n }\n res.status(200).json({ ...result, jwt: token });\n return;\n } catch (e: any) {\n // If session issuance fails, still return verification result\n }\n }\n res.status(200).json(result);\n } catch (e: any) {\n res.status(500).json({ code: 'internal', message: e?.message || 'Internal error' });\n }\n });\n\n // Session: read current claims via bearer token or cookie\n router.get(mePath, async (req: any, res: any) => {\n try {\n const session = opts.session;\n if (!session) {\n res.status(501).json({ authenticated: false, code: 'sessions_disabled', message: 'Sessions are not configured' });\n return;\n }\n const parsed = await session.parse(req.headers || {});\n if (!parsed.ok) {\n res.status(401).json({ authenticated: false, code: 'unauthorized', message: 'No valid session' });\n return;\n }\n res.status(200).json({ authenticated: true, claims: (parsed as any).claims });\n } catch (e: any) {\n res.status(500).json({ authenticated: false, code: 'internal', message: e?.message || 'Internal error' });\n }\n });\n\n // Session: logout clears cookie (best-effort)\n router.post(logoutPath, async (_req: any, res: any) => {\n try {\n const session = opts.session;\n if (session) res.set('Set-Cookie', session.buildClearCookie());\n res.status(200).json({ success: true });\n } catch (e: any) {\n res.status(500).json({ success: false, error: 'internal', details: e?.message });\n }\n });\n\n // Session: refresh (sliding expiration)\n router.post('/session/refresh', async (req: any, res: any) => {\n try {\n const sessionKind = ((req.body || {}).sessionKind === 'cookie') ? 'cookie' : ((req.body || {}).session_kind === 'cookie' ? 'cookie' : 'jwt');\n const session = opts.session;\n if (!session) {\n res.status(501).json({ code: 'sessions_disabled', message: 'Sessions are not configured' });\n return;\n }\n const out = await session.refresh(req.headers || {});\n if (!out.ok || !out.jwt) {\n const code = out.code || 'not_eligible';\n const message = out.message || 'Refresh not eligible';\n res.status(code === 'unauthorized' ? 401 : 400).json({ code, message });\n return;\n }\n if (sessionKind === 'cookie') {\n res.set('Set-Cookie', session.buildSetCookie(out.jwt));\n res.status(200).json({ ok: true });\n } else {\n res.status(200).json({ ok: true, jwt: out.jwt });\n }\n } catch (e: any) {\n res.status(500).json({ code: 'internal', message: e?.message || 'Internal error' });\n }\n });\n\n // Email recovery hook (DKIM/TEE flow):\n // Accept a ForwardableEmailPayload from the email worker and call the\n // per-user email-recoverer contract deployed on `accountId`.\n router.post('/recover-email', async (req: any, res: any) => {\n try {\n const prefer = String(req?.headers?.prefer || '').toLowerCase();\n const respondAsync =\n prefer.includes('respond-async') ||\n String((req?.query as any)?.async || '').trim() === '1' ||\n String((req?.query as any)?.respond_async || '').trim() === '1';\n\n const parsed = parseRecoverEmailRequest(req.body as unknown, { headers: req.headers as any });\n if (!parsed.ok) {\n res.status(parsed.status).json({ code: parsed.code, message: parsed.message });\n return;\n }\n const { accountId, emailBlob, explicitMode } = parsed;\n\n if (!service.emailRecovery) {\n res.status(503).json({ code: 'email_recovery_unavailable', message: 'EmailRecoveryService is not configured on this server' });\n return;\n }\n\n if (respondAsync) {\n void service.emailRecovery\n .requestEmailRecovery({ accountId, emailBlob, explicitMode })\n .then((result) => {\n logger.info('[recover-email] async complete', {\n success: result?.success === true,\n accountId,\n error: result?.success ? undefined : result?.error,\n });\n })\n .catch((err: any) => {\n logger.error('[recover-email] async error', {\n accountId,\n error: err?.message || String(err),\n });\n });\n\n res.status(202).json({ success: true, queued: true, accountId });\n return;\n }\n\n const result = await service.emailRecovery.requestEmailRecovery({ accountId, emailBlob, explicitMode });\n res.status(result.success ? 202 : 400).json(result);\n } catch (e: any) {\n res.status(500).json({ code: 'internal', message: e?.message || 'Internal error' });\n }\n });\n\n router.get('/shamir/key-info', async (_req: any, res: any) => {\n const shamir = service.shamirService;\n if (!shamir || !shamir.hasShamir()) {\n return res.status(503).json({ error: 'shamir_disabled', message: 'Shamir 3-pass is not configured on this server' });\n }\n try {\n const serverResponse = await handleGetShamirKeyInfo(shamir);\n Object.entries(serverResponse.headers).forEach(([k, v]) => res.set(k, v as any));\n res.status(serverResponse.status);\n res.send(JSON.parse(serverResponse.body));\n } catch (e: any) {\n res.status(500).json({ error: 'internal', details: e?.message });\n }\n });\n\n if (opts.healthz) {\n router.get('/healthz', async (_req: Request, res: Response) => {\n const shamir = service.shamirService;\n const shamirConfigured = Boolean(shamir && shamir.hasShamir());\n let currentKeyId: string | null = null;\n if (shamirConfigured && shamir) {\n try {\n const payload = JSON.parse((await handleGetShamirKeyInfo(shamir)).body) as { currentKeyId?: string };\n currentKeyId = payload.currentKeyId || null;\n } catch {}\n }\n\n const proverBaseUrl = service.emailRecovery?.getZkEmailProverBaseUrl?.() ?? null;\n const zkEmailConfigured = Boolean(proverBaseUrl);\n\n res.status(200).json({\n ok: true,\n // Backwards-compatible field (was previously top-level).\n currentKeyId,\n shamir: { configured: shamirConfigured, currentKeyId },\n zkEmail: { configured: zkEmailConfigured, proverBaseUrl },\n });\n });\n }\n\n if (opts.readyz) {\n router.get('/readyz', async (_req: Request, res: Response) => {\n const shamir = service.shamirService;\n const shamirConfigured = Boolean(shamir && shamir.hasShamir());\n\n let shamirReady: boolean | null = null;\n let shamirCurrentKeyId: string | null = null;\n let shamirError: string | undefined;\n if (shamirConfigured && shamir) {\n try {\n await shamir.ensureReady();\n shamirReady = true;\n const payload = JSON.parse((await handleGetShamirKeyInfo(shamir)).body) as { currentKeyId?: string };\n shamirCurrentKeyId = payload.currentKeyId || null;\n } catch (e: any) {\n shamirReady = false;\n shamirError = e?.message || String(e);\n }\n }\n\n const zk = service.emailRecovery\n ? await service.emailRecovery.checkZkEmailProverHealth()\n : { configured: false, baseUrl: null, healthy: null as boolean | null };\n\n const ok =\n (shamirConfigured ? shamirReady === true : true) &&\n (zk.configured ? zk.healthy === true : true);\n\n res.status(ok ? 200 : 503).json({\n ok,\n shamir: {\n configured: shamirConfigured,\n ready: shamirConfigured ? shamirReady : null,\n currentKeyId: shamirCurrentKeyId,\n error: shamirError,\n },\n zkEmail: zk,\n });\n });\n }\n\n // ROR manifest for Related Origin Requests (wallet-scoped credentials)\n const wellKnownPaths = ['/.well-known/webauthn', '/.well-known/webauthn/'];\n for (const p of wellKnownPaths) {\n router.get(p, async (_req: Request, res: Response) => {\n try {\n const origins = await service.getRorOrigins();\n res.set('Content-Type', 'application/json; charset=utf-8');\n // Short TTL + SWR so updates propagate while staying cache-friendly\n res.set('Cache-Control', 'max-age=60, stale-while-revalidate=600');\n res.status(200).send(JSON.stringify({ origins }));\n } catch (e: any) {\n res.status(200).json({ origins: [] });\n }\n });\n }\n\n return router;\n}\n\nexport interface KeyRotationCronOptions {\n enabled?: boolean;\n intervalMinutes?: number;\n maxGraceKeys?: number;\n logger?: RouterLogger | null;\n}\n\nexport function startKeyRotationCronjob(\n service: AuthService,\n opts: KeyRotationCronOptions = {},\n): { stop(): void } {\n const logger = normalizeRouterLogger(opts.logger);\n const enabled = Boolean(opts.enabled);\n const intervalMinutes = Math.max(1, Number(opts.intervalMinutes || 0) || 60);\n const maxGraceKeys = Math.max(0, Number(opts.maxGraceKeys ?? 0) || 0);\n\n let timer: any = null;\n let inFlight = false;\n\n const run = async () => {\n if (!enabled) return;\n if (inFlight) {\n logger.warn('[key-rotation-cron] previous rotation still running; skipping');\n return;\n }\n inFlight = true;\n try {\n const shamir = service.shamirService;\n if (!shamir || !shamir.hasShamir()) {\n logger.warn('[key-rotation-cron] Shamir not configured; skipping rotation');\n return;\n }\n\n const rotation = await shamir.rotateShamirServerKeypair();\n logger.info('[key-rotation-cron] rotated key', {\n newKeyId: rotation.newKeyId,\n previousKeyId: rotation.previousKeyId,\n graceKeyIds: rotation.graceKeyIds,\n });\n\n if (maxGraceKeys > 0) {\n const graceKeyIds = shamir.getGraceKeyIds();\n if (graceKeyIds.length > maxGraceKeys) {\n const toRemove = graceKeyIds.slice(0, graceKeyIds.length - maxGraceKeys);\n for (const keyId of toRemove) {\n try {\n const removed = await shamir.removeGraceKeyInternal(keyId, { persist: true });\n logger.info('[key-rotation-cron] pruned grace key', { keyId, removed });\n } catch (e: any) {\n logger.warn('[key-rotation-cron] failed to prune grace key', { keyId, error: e?.message || String(e) });\n }\n }\n }\n }\n } catch (e: any) {\n logger.error('[key-rotation-cron] rotation failed', { error: e?.message || String(e) });\n } finally {\n inFlight = false;\n }\n };\n\n if (enabled) {\n timer = setInterval(() => { void run(); }, intervalMinutes * 60_000);\n }\n\n return {\n stop() {\n if (timer) clearInterval(timer);\n timer = null;\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"express.js","names":["out: ShamirApplyServerLockResponse","e: any","out: ShamirRemoveServerLockResponse","normalizedHeaders: Record<string, string>","v","base: Logger","allowedOrigin: string | '*' | undefined","error: any","e: any","result","currentKeyId: string | null","shamirReady: boolean | null","shamirCurrentKeyId: string | null","shamirError: string | undefined","timer: any"],"sources":["../../../../src/server/core/SessionService.ts","../../../../src/core/WalletIframe/validation.ts","../../../../src/server/core/shamirHandlers.ts","../../../../src/server/email-recovery/zkEmail/index.ts","../../../../src/server/email-recovery/emailParsers.ts","../../../../src/server/core/logger.ts","../../../../src/server/router/logger.ts","../../../../src/server/router/express-adaptor.ts"],"sourcesContent":["\nexport interface SessionConfig {\n jwt?: {\n /** Required: JWT signing hook; return a complete token */\n signToken?: (input: { header: Record<string, unknown>; payload: Record<string, unknown> }) => Promise<string> | string;\n /** Required: JWT verification hook */\n verifyToken?: (token: string) => Promise<{ valid: boolean; payload?: any }> | { valid: boolean; payload?: any };\n /** Optional: sliding refresh window (seconds) to allow /session/refresh before exp, default 900 (15 min) */\n refreshWindowSec?: number;\n /** Optional: build additional claims to include in the payload */\n buildClaims?: (input: { sub: string; context?: Record<string, unknown> }) => Promise<Record<string, unknown> | void> | Record<string, unknown> | void;\n };\n cookie?: {\n /** Cookie name. Default: 'w3a_session' */\n name?: string;\n /** Optional override: build Set-Cookie header for a new token */\n buildSetHeader?: (token: string) => string;\n /** Optional override: build Set-Cookie header that clears the cookie */\n buildClearHeader?: () => string;\n /** Optional override: extract token from headers (Authorization/Cookie) */\n extractToken?: (headers: Record<string, string | string[] | undefined>, cookieName: string) => string | null;\n };\n}\n\nexport class SessionService<TClaims = any> {\n private cfg: NonNullable<SessionConfig>;\n\n constructor(cfg: NonNullable<SessionConfig>) {\n this.cfg = cfg || ({} as any);\n }\n\n getCookieName(): string {\n return this.cfg?.cookie?.name || 'w3a_session';\n }\n\n buildSetCookie(token: string): string {\n if (this.cfg?.cookie?.buildSetHeader) return this.cfg.cookie.buildSetHeader(token);\n const name = this.getCookieName();\n const cookieParts = [`${name}=${token}`];\n const path = '/';\n const httpOnly = true;\n const secure = true; // default secure\n const sameSite = 'Lax';\n const maxAge = 24 * 3600; // 1 day default\n cookieParts.push(`Path=${path}`);\n if (httpOnly) cookieParts.push('HttpOnly');\n if (secure) cookieParts.push('Secure');\n if (sameSite) cookieParts.push(`SameSite=${sameSite}`);\n if (maxAge) {\n cookieParts.push(`Max-Age=${maxAge}`);\n const expires = new Date(Date.now() + (maxAge * 1000)).toUTCString();\n cookieParts.push(`Expires=${expires}`);\n }\n return cookieParts.join('; ');\n }\n\n buildClearCookie(): string {\n if (this.cfg?.cookie?.buildClearHeader) return this.cfg.cookie.buildClearHeader();\n const name = this.getCookieName();\n const path = '/';\n const secure = true;\n const httpOnly = true;\n const parts = [\n `${name}=`,\n `Path=${path}`,\n 'Max-Age=0',\n 'Expires=Thu, 01 Jan 1970 00:00:00 GMT'\n ];\n if (httpOnly) parts.push('HttpOnly');\n if (secure) parts.push('Secure');\n const sameSite = 'Lax';\n if (sameSite) parts.push(`SameSite=${sameSite}`);\n return parts.join('; ');\n }\n\n /** Sign a JWT with configured algorithm. Adds iat/exp and copies iss/aud. */\n async signJwt(sub: string, extraClaims?: Record<string, unknown>): Promise<string> {\n const jwt = this.cfg?.jwt || {};\n const built = await Promise.resolve(jwt.buildClaims?.({ sub, context: extraClaims })) || {};\n const payload = { sub, ...(extraClaims || {}), ...(built || {}) } as Record<string, unknown>;\n if (typeof jwt.signToken === 'function') {\n // Full override of signing: user supplies the complete token\n const token = await Promise.resolve(jwt.signToken({ header: { typ: 'JWT' }, payload } as any));\n return token;\n }\n throw new Error('SessionService: No JWT signing hook or provider configured');\n }\n\n /** Verify signature and expiration. Returns payload on success. */\n async verifyJwt(token: string): Promise<{ valid: boolean; payload?: any }> {\n const verify = this.cfg?.jwt?.verifyToken;\n if (typeof verify !== 'function') return { valid: false };\n return await Promise.resolve(verify(token));\n }\n\n parse(headers: Record<string, string | string[] | undefined>): Promise<{ ok: boolean; claims?: TClaims } | { ok: false }>{\n const authHeader = (headers['authorization'] || headers['Authorization']) as string | undefined;\n let token: string | null = null;\n if (authHeader && /^Bearer\\s+/.test(authHeader)) token = authHeader.replace(/^Bearer\\s+/i, '').trim();\n const cookieHeader = (headers['cookie'] || headers['Cookie']) as string | undefined;\n if (!token && cookieHeader) {\n const name = this.getCookieName();\n for (const part of cookieHeader.split(';')) {\n const [k, v] = part.split('=');\n if (k && k.trim() === name) { token = (v || '').trim(); break; }\n }\n }\n if (!token) return Promise.resolve({ ok: false });\n return this.verifyJwt(token).then(v => v.valid ? { ok: true, claims: v.payload as TClaims } : { ok: false });\n }\n\n // === token helpers ===\n extractTokenFromHeaders(headers: Record<string, string | string[] | undefined>): string | null {\n if (this.cfg?.cookie?.extractToken) return this.cfg.cookie.extractToken(headers, this.getCookieName());\n const authHeader = (headers['authorization'] || headers['Authorization']) as string | undefined;\n if (authHeader && /^Bearer\\s+/.test(authHeader)) return authHeader.replace(/^Bearer\\s+/i, '').trim();\n const cookieHeader = (headers['cookie'] || headers['Cookie']) as string | undefined;\n if (cookieHeader) {\n const name = this.getCookieName();\n for (const part of cookieHeader.split(';')) {\n const [k, v] = part.split('=');\n if (k && k.trim() === name) return (v || '').trim();\n }\n }\n return null;\n }\n\n async refresh(headers: Record<string, string | string[] | undefined>): Promise<{ ok: boolean; jwt?: string; code?: string; message?: string }>{\n try {\n const token = this.extractTokenFromHeaders(headers);\n if (!token) return { ok: false, code: 'unauthorized', message: 'No session token' };\n const v = await this.verifyJwt(token);\n if (!v.valid) return { ok: false, code: 'unauthorized', message: 'Invalid token' };\n const payload: any = v.payload || {};\n if (!this.isWithinRefreshWindow(payload)) return { ok: false, code: 'not_eligible', message: 'Not within refresh window' };\n const sub = String(payload.sub || '');\n if (!sub) return { ok: false, code: 'invalid_claims', message: 'Missing sub claim' };\n const next = await this.signJwt(sub);\n return { ok: true, jwt: next };\n } catch (e: any) {\n return { ok: false, code: 'internal', message: e?.message || 'Refresh failed' };\n }\n }\n\n nowSeconds(): number { return Math.floor(Date.now() / 1000); }\n\n private isWithinRefreshWindow(payload: any): boolean {\n try {\n const now = this.nowSeconds();\n const exp = Number(payload?.exp || 0);\n if (!exp || now >= exp) return false; // no refresh if already expired\n const windowSec = Number(this.cfg?.jwt?.refreshWindowSec || 15 * 60);\n return (exp - now) <= windowSec;\n } catch { return false; }\n }\n}\n\n/*\n * Utility: parse comma-separated list of origins into a normalized unique list\n * - canonicalizes to protocol + host + optional port\n * - lowercases host, strips path/query/hash, trims spaces/trailing slashes\n */\nexport function parseCsvList(input?: string): string[] {\n const out = new Set<string>();\n for (const raw of String(input || '').split(',')) {\n const s = raw.trim();\n if (!s) continue;\n try {\n const u = new URL(s);\n const host = u.hostname.toLowerCase();\n const port = u.port ? `:${u.port}` : '';\n const proto = u.protocol === 'http:' || u.protocol === 'https:' ? u.protocol : 'https:';\n out.add(`${proto}//${host}${port}`);\n } catch {\n const stripped = s.replace(/\\/$/, '');\n if (stripped) out.add(stripped);\n }\n }\n return Array.from(out);\n}\n\n/*\n * Utility: merge multiple CSV lists of origins and return normalized list or '*'\n */\nexport function buildCorsOrigins(...inputs: Array<string | undefined>): string[] | '*' {\n const merged = new Set<string>();\n for (const input of inputs) {\n for (const origin of parseCsvList(input)) merged.add(origin);\n }\n const list = Array.from(merged);\n return list.length > 0 ? list : '*';\n}\n","// Shared runtime validation helpers for Wallet Iframe code.\n\nexport function isObject(x: unknown): x is Record<string, unknown> {\n return x !== null && typeof x === 'object';\n}\n\nexport function isString(x: unknown): x is string {\n return typeof x === 'string';\n}\n\nexport function isNonEmptyString(x: unknown): x is string {\n return typeof x === 'string' && x.length > 0;\n}\n\nexport function isNumber(x: unknown): x is number {\n return typeof x === 'number';\n}\n\nexport function isFiniteNumber(x: unknown): x is number {\n return typeof x === 'number' && Number.isFinite(x);\n}\n\nexport function isFunction(x: unknown): x is Function {\n return typeof x === 'function';\n}\n\nexport function isBoolean(x: unknown): x is boolean {\n return typeof x === 'boolean';\n}\n\nexport function isArray<T = unknown>(x: unknown): x is T[] {\n return Array.isArray(x);\n}\n\n// ========= Assertions (throw on mismatch) =========\n\nexport function assertString(val: unknown, name = 'value'): string {\n if (typeof val !== 'string') throw new Error(`Invalid ${name}: expected string`);\n return val;\n}\n\nexport function assertNumber(val: unknown, name = 'value'): number {\n if (typeof val !== 'number' || !Number.isFinite(val)) throw new Error(`Invalid ${name}: expected finite number`);\n return val;\n}\n\nexport function assertBoolean(val: unknown, name = 'value'): boolean {\n if (typeof val !== 'boolean') throw new Error(`Invalid ${name}: expected boolean`);\n return val;\n}\n\nexport function assertObject<T extends Record<string, unknown> = Record<string, unknown>>(val: unknown, name = 'value'): T {\n if (!isObject(val)) throw new Error(`Invalid ${name}: expected object`);\n return val as T;\n}\n\nexport function assertArray<T = unknown>(val: unknown, name = 'value'): T[] {\n if (!Array.isArray(val)) throw new Error(`Invalid ${name}: expected array`);\n return val as T[];\n}\n\n// Shallowly remove function-valued properties (postMessage/clone safety)\nexport function stripFunctionsShallow<T extends Record<string, unknown>>(obj?: T): Partial<T> | undefined {\n if (!obj || !isObject(obj)) return undefined;\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (!isFunction(v)) out[k] = v as unknown;\n }\n return out as Partial<T>;\n}\n\n// ===============================\n// SignedTransaction shape helpers\n// ===============================\n\nexport interface PlainSignedTransactionLike {\n transaction: unknown;\n signature: unknown;\n borsh_bytes?: unknown;\n borshBytes?: unknown;\n base64Encode?: unknown;\n}\n\nexport function isPlainSignedTransactionLike(x: unknown): x is PlainSignedTransactionLike {\n if (!isObject(x)) return false;\n const hasTx = 'transaction' in x;\n const hasSig = 'signature' in x;\n const bytes = x as { borsh_bytes?: unknown; borshBytes?: unknown };\n const hasBytes = Array.isArray(bytes.borsh_bytes) || bytes.borshBytes instanceof Uint8Array;\n const hasMethod = typeof (x as { base64Encode?: unknown }).base64Encode === 'function';\n return hasTx && hasSig && hasBytes && !hasMethod;\n}\n\nexport function extractBorshBytesFromPlainSignedTx(x: PlainSignedTransactionLike): number[] {\n const asArray = Array.isArray(x.borsh_bytes) ? (x.borsh_bytes as number[]) : undefined;\n if (asArray) return asArray;\n const asU8 = (x.borshBytes instanceof Uint8Array) ? x.borshBytes : undefined;\n return Array.from(asU8 || new Uint8Array());\n}\n","import type {\n ShamirApplyServerLockRequest,\n ShamirApplyServerLockResponse,\n ShamirRemoveServerLockRequest,\n ShamirRemoveServerLockResponse,\n} from './types';\nimport type { ShamirService } from './ShamirService';\nimport { isString } from '../../core/WalletIframe/validation';\n\nexport async function handleApplyServerLock(\n service: ShamirService,\n request: { body?: { kek_c_b64u?: string } },\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n const kek_c_b64u = request.body?.kek_c_b64u;\n if (!isString(kek_c_b64u) || !kek_c_b64u) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'kek_c_b64u required and must be a non-empty string' }),\n };\n }\n\n const out: ShamirApplyServerLockResponse = await service.applyServerLock(kek_c_b64u);\n const keyId = service.getCurrentShamirKeyId();\n\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ ...out, keyId }),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n\nexport async function handleRemoveServerLock(\n service: ShamirService,\n request: { body?: { kek_cs_b64u?: string; keyId?: string } },\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n const body = request.body;\n if (!body) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'Missing body' }),\n };\n }\n const { kek_cs_b64u, keyId } = body;\n if (!isString(kek_cs_b64u) || !kek_cs_b64u) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'kek_cs_b64u required and must be a non-empty string' }),\n };\n }\n if (!isString(keyId) || !keyId) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'keyId required and must be a non-empty string' }),\n };\n }\n\n const providedKeyId = String(keyId);\n const currentKeyId = service.getCurrentShamirKeyId();\n let out: ShamirRemoveServerLockResponse;\n\n if (currentKeyId && providedKeyId === currentKeyId) {\n out = await service.removeServerLock(kek_cs_b64u);\n } else if (service.hasGraceKey(providedKeyId)) {\n out = await service.removeGraceServerLockWithKey(providedKeyId, { kek_cs_b64u } as ShamirRemoveServerLockRequest);\n } else {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'unknown keyId' }),\n };\n }\n\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(out),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n\nexport async function handleGetShamirKeyInfo(\n service: ShamirService,\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n await service.ensureReady();\n const currentKeyId = service.getCurrentShamirKeyId();\n const graceKeyIds = service.getGraceKeyIds();\n\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n currentKeyId,\n p_b64u: service.getShamirConfig()?.shamir_p_b64u ?? null,\n graceKeyIds,\n }),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n\nexport async function handleListGraceKeys(\n service: ShamirService,\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n await service.ensureGraceKeysLoaded();\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ graceKeyIds: service.getGraceKeyIds() }),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n\nexport async function handleAddGraceKey(\n service: ShamirService,\n request: { e_s_b64u?: string; d_s_b64u?: string },\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n const { e_s_b64u, d_s_b64u } = request || ({} as any);\n if (!isString(e_s_b64u) || !e_s_b64u || !isString(d_s_b64u) || !d_s_b64u) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'e_s_b64u and d_s_b64u required' }),\n };\n }\n\n await service.ensureGraceKeysLoaded();\n const added = await service.addGraceKeyInternal({ e_s_b64u, d_s_b64u }, { persist: true, skipIfExists: true });\n if (!added) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'failed to add grace key' }),\n };\n }\n\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ keyId: added.keyId }),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n\nexport async function handleRemoveGraceKey(\n service: ShamirService,\n request: { keyId?: string },\n): Promise<{ status: number; headers: Record<string, string>; body: string }> {\n try {\n const keyId = request?.keyId;\n if (!isString(keyId) || !keyId) {\n return {\n status: 400,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'keyId required and must be a non-empty string' }),\n };\n }\n\n const removed = await service.removeGraceKeyInternal(keyId, { persist: true });\n return {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ removed }),\n };\n } catch (e: any) {\n return {\n status: 500,\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ error: 'internal', details: e?.message }),\n };\n }\n}\n","import { ZkEmailProverClient, type ZkEmailProverClientOptions, type ZkEmailProverError } from './proverClient';\n\nexport { ZkEmailProverClient } from './proverClient';\nexport type { ZkEmailProverClientOptions, ZkEmailProverError, ZkEmailProverResponse } from './proverClient';\n\nexport interface ForwardableEmailPayload {\n from: string;\n to: string;\n headers: Record<string, string>;\n raw?: string;\n rawSize?: number;\n}\n\nexport type NormalizedEmailResult =\n | { ok: true; payload: ForwardableEmailPayload }\n | { ok: false; code: string; message: string };\n\nexport interface GenerateZkEmailProofResult {\n proof: unknown;\n publicInputs: string[];\n}\n\nexport interface ParsedZkEmailBindings {\n accountId: string;\n newPublicKey: string;\n fromEmail: string;\n timestamp: string;\n requestId: string;\n}\n\n/**\n * Build a minimal ForwardableEmailPayload from a raw RFC822 email string.\n * This is primarily used by server-side helpers that receive only a raw\n * email blob (no pre-normalized headers).\n */\nexport function buildForwardablePayloadFromRawEmail(raw: string): ForwardableEmailPayload {\n const safeRaw = typeof raw === 'string' ? raw : '';\n const lines = safeRaw.split(/\\r?\\n/);\n\n const getHeader = (name: string): string | undefined => {\n const line = lines.find(l => new RegExp(`^${name}:`, 'i').test(l));\n if (!line) return undefined;\n const idx = line.indexOf(':');\n const rest = idx >= 0 ? line.slice(idx + 1) : '';\n const value = rest.trim();\n return value || undefined;\n };\n\n const fromHeader = getHeader('from') || 'unknown@zkemail.local';\n const toHeader = getHeader('to') || 'recover@zkemail.local';\n\n const headers: Record<string, string> = {};\n const subjectHeader = getHeader('subject');\n const dateHeader = getHeader('date');\n\n if (fromHeader) headers.from = fromHeader;\n if (toHeader) headers.to = toHeader;\n if (subjectHeader) headers.subject = subjectHeader;\n if (dateHeader) headers.date = dateHeader;\n\n return {\n from: fromHeader,\n to: toHeader,\n headers,\n raw: safeRaw,\n rawSize: safeRaw.length,\n };\n}\n\nexport function normalizeForwardableEmailPayload(input: unknown): NormalizedEmailResult {\n if (!input || typeof input !== 'object') {\n return { ok: false, code: 'invalid_email', message: 'JSON body required' };\n }\n\n const body = input as Partial<ForwardableEmailPayload>;\n const { from, to, headers, raw, rawSize } = body;\n\n if (!from || typeof from !== 'string' || !to || typeof to !== 'string') {\n return { ok: false, code: 'invalid_email', message: 'from and to are required' };\n }\n\n if (!headers || typeof headers !== 'object') {\n return { ok: false, code: 'invalid_email', message: 'headers object is required' };\n }\n\n const normalizedHeaders: Record<string, string> = {};\n for (const [k, v] of Object.entries(headers as Record<string, unknown>)) {\n normalizedHeaders[String(k).toLowerCase()] = String(v);\n }\n\n return {\n ok: true,\n payload: {\n from,\n to,\n headers: normalizedHeaders,\n raw: typeof raw === 'string' ? raw : undefined,\n rawSize: typeof rawSize === 'number' ? rawSize : undefined,\n },\n };\n}\n\n/**\n * Parse NEAR accountId from the Subject line inside a raw RFC822 email.\n *\n * Expected format (case-insensitive on \"Subject\" and \"recover\"):\n * Subject: recover-123ABC bob.testnet ed25519:<pk>\n *\n * Returns the parsed accountId (e.g. \"bob.testnet\") or null if not found.\n */\nexport function parseAccountIdFromSubject(raw: string | undefined | null): string | null {\n if (!raw || typeof raw !== 'string') return null;\n\n // Accept either a full RFC822 message (with \"Subject: ...\" header)\n // or a bare Subject value (\"recover-123ABC bob.testnet ed25519:<pk>\").\n let subjectText = '';\n const lines = raw.split(/\\r?\\n/);\n const subjectLine = lines.find(line => /^subject:/i.test(line));\n if (subjectLine) {\n const idx = subjectLine.indexOf(':');\n const restRaw = idx >= 0 ? subjectLine.slice(idx + 1) : '';\n subjectText = restRaw.trim();\n } else {\n subjectText = raw.trim();\n }\n\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: \"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?.[2]) {\n return match[2];\n }\n\n return null;\n}\n\nfunction parseSubjectBindings(\n rawSubject: string | undefined | null\n): { accountId: string; newPublicKey: string; requestId: string } | null {\n if (!rawSubject || typeof rawSubject !== 'string') return null;\n\n const lines = rawSubject.split(/\\r?\\n/);\n let subjectText = '';\n const subjectLine = lines.find(line => /^subject:/i.test(line));\n if (subjectLine) {\n const idx = subjectLine.indexOf(':');\n const restRaw = idx >= 0 ? subjectLine.slice(idx + 1) : '';\n subjectText = restRaw.trim();\n } else {\n subjectText = rawSubject.trim();\n }\n if (!subjectText) return null;\n\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\n return {\n accountId,\n newPublicKey,\n requestId,\n };\n}\n\nexport function extractZkEmailBindingsFromPayload(\n payload: ForwardableEmailPayload\n): ParsedZkEmailBindings | null {\n const raw = payload.raw || '';\n const lines = raw.split(/\\r?\\n/);\n\n const subjectLine = lines.find(line => /^subject:/i.test(line));\n const subjectBindings = parseSubjectBindings(subjectLine ?? '');\n if (!subjectBindings) {\n return null;\n }\n\n const headers = payload.headers || {};\n let fromEmailRaw: string | undefined =\n (headers['from'] as any) ||\n (headers['x-from-email'] as any);\n let dateRaw: string | undefined =\n (headers['date'] as any) ||\n (headers['x-original-date'] as any);\n\n // Fallback: if headers object does not contain from/date,\n // attempt to parse them from the raw RFC822 email lines.\n if (!fromEmailRaw || !dateRaw) {\n for (const line of lines) {\n if (!fromEmailRaw && /^from:/i.test(line)) {\n const idx = line.indexOf(':');\n fromEmailRaw = idx >= 0 ? line.slice(idx + 1).trim() : '';\n }\n if (!dateRaw && /^date:/i.test(line)) {\n const idx = line.indexOf(':');\n dateRaw = idx >= 0 ? line.slice(idx + 1).trim() : '';\n }\n if (fromEmailRaw && dateRaw) break;\n }\n }\n\n const fromEmail = String(fromEmailRaw || '').trim();\n const timestamp = String(dateRaw || '').trim();\n\n if (!fromEmail || !timestamp) {\n return null;\n }\n\n return {\n accountId: subjectBindings.accountId,\n newPublicKey: subjectBindings.newPublicKey,\n fromEmail,\n timestamp,\n requestId: subjectBindings.requestId,\n };\n}\n\nexport async function generateZkEmailProofFromPayload(\n payload: ForwardableEmailPayload,\n prover: ZkEmailProverClientOptions | ZkEmailProverClient\n): Promise<GenerateZkEmailProofResult> {\n if (!payload.raw || typeof payload.raw !== 'string') {\n const err: ZkEmailProverError = Object.assign(\n new Error('raw email contents are required to generate a zk-email proof'),\n { code: 'missing_raw_email' }\n );\n throw err;\n }\n\n const client =\n (prover && typeof (prover as any).proveEmail === 'function')\n ? (prover as ZkEmailProverClient)\n : new ZkEmailProverClient(prover as ZkEmailProverClientOptions);\n\n const res = await client.proveEmail(payload.raw);\n\n return {\n proof: res.proof,\n publicInputs: res.publicSignals,\n };\n}\n","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","export type Logger = {\n debug?: (...args: unknown[]) => void;\n info?: (...args: unknown[]) => void;\n warn?: (...args: unknown[]) => void;\n error?: (...args: unknown[]) => void;\n log?: (...args: unknown[]) => void;\n};\n\nexport type NormalizedLogger = {\n debug: (...args: unknown[]) => void;\n info: (...args: unknown[]) => void;\n warn: (...args: unknown[]) => void;\n error: (...args: unknown[]) => void;\n};\n\nfunction safe(fn?: (...args: unknown[]) => void): (...args: unknown[]) => void {\n if (!fn) return () => {};\n return (...args: unknown[]) => {\n try {\n fn(...args);\n } catch {\n // Never allow logging to break request handling.\n }\n };\n}\n\n/**\n * Library code should never call `console.*` directly; the host decides where logs go.\n * - Default: no logs\n * - To enable: pass `logger: console` (or a structured logger) to the host config.\n */\nexport function normalizeLogger(logger?: Logger | null): NormalizedLogger {\n if (!logger) {\n return { debug: () => {}, info: () => {}, warn: () => {}, error: () => {} };\n }\n\n const base: Logger = logger;\n const log = typeof base.log === 'function' ? base.log.bind(base) : undefined;\n\n const debug = (typeof base.debug === 'function' ? base.debug.bind(base) : log);\n const info = (typeof base.info === 'function' ? base.info.bind(base) : log);\n const warn = (typeof base.warn === 'function' ? base.warn.bind(base) : log);\n const error = (typeof base.error === 'function' ? base.error.bind(base) : log);\n\n return {\n debug: safe(debug),\n info: safe(info),\n warn: safe(warn),\n error: safe(error),\n };\n}\n\n","import type { Logger, NormalizedLogger } from '../core/logger';\nimport { normalizeLogger } from '../core/logger';\n\nexport type RouterLogger = Logger;\nexport type NormalizedRouterLogger = NormalizedLogger;\nexport const normalizeRouterLogger = normalizeLogger;\n","import type { Request, Response, Router as ExpressRouter } from 'express';\nimport express from 'express';\nimport type { AuthService } from '../core/AuthService';\nimport { buildCorsOrigins } from '../core/SessionService';\nimport {\n handleApplyServerLock,\n handleRemoveServerLock,\n handleGetShamirKeyInfo,\n handleListGraceKeys,\n handleAddGraceKey,\n handleRemoveGraceKey,\n} from '../core/shamirHandlers';\nimport { parseRecoverEmailRequest } from '../email-recovery/emailParsers';\nimport type { DelegateActionPolicy } from '../delegateAction';\nimport type { RouterLogger } from './logger';\nimport { normalizeRouterLogger } from './logger';\n\nexport interface RelayRouterOptions {\n healthz?: boolean;\n readyz?: boolean;\n // Optional list(s) of CORS origins (CSV strings or literal origins).\n // Pass raw strings; the router normalizes/merges internally.\n corsOrigins?: Array<string | undefined>;\n /**\n * Optional route for submitting NEP-461 SignedDelegate meta-transactions.\n *\n * - When omitted: disabled.\n * - When set: enabled at `route`.\n *\n * `policy` is server-controlled and is never read from the request body.\n */\n signedDelegate?: {\n route: string;\n policy?: DelegateActionPolicy;\n };\n sessionRoutes?: { auth?: string; logout?: string };\n session?: SessionAdapter | null;\n // Optional logger; defaults to silent.\n logger?: RouterLogger | null;\n}\n\nfunction normalizePath(path: string): string {\n const trimmed = String(path || '').trim();\n if (!trimmed) return '';\n return trimmed.startsWith('/') ? trimmed : `/${trimmed}`;\n}\n\nfunction withCors(res: Response, opts?: RelayRouterOptions, req?: Request): void {\n if (!opts?.corsOrigins) return;\n\n let allowedOrigin: string | '*' | undefined;\n const normalized = buildCorsOrigins(...(opts.corsOrigins || []));\n if (normalized === '*') {\n allowedOrigin = '*';\n res.set('Access-Control-Allow-Origin', '*');\n } else if (Array.isArray(normalized)) {\n const origin = String((req as any)?.headers?.origin || '').trim();\n if (origin && normalized.includes(origin)) {\n allowedOrigin = origin;\n res.set('Access-Control-Allow-Origin', origin);\n res.set('Vary', 'Origin');\n }\n }\n\n res.set('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');\n res.set('Access-Control-Allow-Headers', 'Content-Type,Authorization');\n // Only advertise credentials when we echo back a specific origin (not '*')\n if (allowedOrigin && allowedOrigin !== '*') {\n res.set('Access-Control-Allow-Credentials', 'true');\n }\n}\n\n// Minimal session adapter interface expected by the routers\nexport interface SessionAdapter {\n signJwt(sub: string, extra?: Record<string, unknown>): Promise<string>;\n parse(headers: Record<string, string | string[] | undefined>): Promise<{ ok: boolean; claims?: any } | { ok: false }>;\n buildSetCookie(token: string): string;\n buildClearCookie(): string;\n refresh(headers: Record<string, string | string[] | undefined>): Promise<{ ok: boolean; jwt?: string; code?: string; message?: string }>;\n}\n\nexport function createRelayRouter(service: AuthService, opts: RelayRouterOptions = {}): ExpressRouter {\n const router = express.Router();\n const mePath = opts.sessionRoutes?.auth || '/session/auth';\n const logoutPath = opts.sessionRoutes?.logout || '/session/logout';\n const logger = normalizeRouterLogger(opts.logger);\n const signedDelegatePath = (() => {\n if (!opts.signedDelegate) return '';\n const raw = String(opts.signedDelegate.route || '').trim();\n if (!raw) throw new Error('RelayRouterOptions.signedDelegate.route is required');\n return normalizePath(raw);\n })();\n const signedDelegatePolicy = opts.signedDelegate?.policy;\n\n // Optional CORS: implemented here to keep setup simple for example relayers.\n // If you prefer custom CORS middleware, omit `corsOrigins` and wire your own.\n router.use((req: Request, res: Response, next: any) => {\n withCors(res, opts, req);\n const method = String((req as any)?.method || '').toUpperCase();\n if (opts.corsOrigins && method === 'OPTIONS') {\n res.status(204).send('');\n return;\n }\n next();\n });\n\n router.post(\n '/create_account_and_register_user',\n async (req: any, res: any) => {\n try {\n const {\n new_account_id,\n new_public_key,\n vrf_data,\n webauthn_registration,\n deterministic_vrf_public_key,\n authenticator_options\n } = req.body || ({} as any);\n\n if (!new_account_id || typeof new_account_id !== 'string') throw new Error('Missing or invalid new_account_id');\n if (!new_public_key || typeof new_public_key !== 'string') throw new Error('Missing or invalid new_public_key');\n if (!vrf_data || typeof vrf_data !== 'object') throw new Error('Missing or invalid vrf_data');\n if (!webauthn_registration || typeof webauthn_registration !== 'object') throw new Error('Missing or invalid webauthn_registration');\n\n const result = await service.createAccountAndRegisterUser({\n new_account_id,\n new_public_key,\n vrf_data,\n webauthn_registration,\n deterministic_vrf_public_key,\n authenticator_options\n });\n\n if (result.success) res.status(200).json(result);\n else res.status(400).json(result);\n } catch (error: any) {\n res.status(500).json({ success: false, error: error?.message || 'internal error' });\n }\n }\n );\n\n if (signedDelegatePath) {\n router.options(signedDelegatePath, (_req: any, res: any) => {\n res.sendStatus(204);\n });\n\n router.post(signedDelegatePath, async (req: any, res: any) => {\n try {\n const { hash, signedDelegate } = req.body || {};\n if (typeof hash !== 'string' || !hash || !signedDelegate) {\n res.status(400).json({ ok: false, code: 'invalid_body', message: 'Expected { hash, signedDelegate }' });\n return;\n }\n\n const result = await service.executeSignedDelegate({\n hash,\n signedDelegate,\n policy: signedDelegatePolicy,\n });\n\n if (!result || !result.ok) {\n res.status(400).json({\n ok: false,\n code: result?.code || 'delegate_execution_failed',\n message: result?.error || 'Failed to execute delegate action',\n });\n return;\n }\n\n res.status(200).json({\n ok: true,\n relayerTxHash: result.transactionHash || null,\n status: 'submitted',\n outcome: result.outcome ?? null,\n });\n } catch (e: any) {\n res.status(500).json({\n ok: false,\n code: 'internal',\n message: e?.message || 'Internal error while executing delegate action',\n });\n }\n });\n }\n\n router.post('/vrf/apply-server-lock', async (req: any, res: any) => {\n const shamir = service.shamirService;\n if (!shamir || !shamir.hasShamir()) {\n return res.status(503).json({ error: 'shamir_disabled', message: 'Shamir 3-pass is not configured on this server' });\n }\n try {\n const serverResponse = await handleApplyServerLock(shamir, { body: req.body });\n Object.entries(serverResponse.headers).forEach(([k, v]) => res.set(k, v as any));\n res.status(serverResponse.status);\n res.send(JSON.parse(serverResponse.body));\n } catch (e: any) {\n res.status(500).json({ error: 'internal', details: e?.message });\n }\n });\n\n router.post('/vrf/remove-server-lock', async (req: any, res: any) => {\n const shamir = service.shamirService;\n if (!shamir || !shamir.hasShamir()) {\n return res.status(503).json({ error: 'shamir_disabled', message: 'Shamir 3-pass is not configured on this server' });\n }\n try {\n const serverResponse = await handleRemoveServerLock(shamir, { body: req.body });\n Object.entries(serverResponse.headers).forEach(([k, v]) => res.set(k, v as any));\n res.status(serverResponse.status);\n res.send(JSON.parse(serverResponse.body));\n } catch (e: any) {\n res.status(500).json({ error: 'internal', details: e?.message });\n }\n });\n\n // VRF + WebAuthn session verification (VIEW call) + optional session issuance\n router.post('/verify-authentication-response', async (req: any, res: any) => {\n try {\n if (!req?.body) {\n res.status(400).json({ code: 'invalid_body', message: 'Request body is required' });\n return;\n }\n const body = req.body;\n const valid = body && body.vrf_data && body.webauthn_authentication;\n if (!valid) {\n res.status(400).json({ code: 'invalid_body', message: 'vrf_data and webauthn_authentication are required' });\n return;\n }\n const result = await service.verifyAuthenticationResponse(body);\n const status = result.success ? 200 : 400;\n if (status !== 200) {\n res.status(status).json({ code: 'not_verified', message: result.message || 'Authentication verification failed' });\n return;\n }\n const sessionKind = ((body?.sessionKind || body?.session_kind) === 'cookie') ? 'cookie' : 'jwt';\n const session = opts.session;\n if (session && result.verified) {\n try {\n const sub = String(body.vrf_data.user_id || '');\n const token = await session.signJwt(sub, { rpId: body.vrf_data.rp_id, blockHeight: body.vrf_data.block_height });\n // Best-effort server log without sensitive data\n logger.info(`[relay] creating ${sessionKind === 'cookie' ? 'HttpOnly session' : 'JWT'} for`, sub);\n if (sessionKind === 'cookie') {\n res.set('Set-Cookie', session.buildSetCookie(token));\n const { jwt: _omit, ...rest } = result as any;\n res.status(200).json(rest);\n return;\n }\n res.status(200).json({ ...result, jwt: token });\n return;\n } catch (e: any) {\n // If session issuance fails, still return verification result\n }\n }\n res.status(200).json(result);\n } catch (e: any) {\n res.status(500).json({ code: 'internal', message: e?.message || 'Internal error' });\n }\n });\n\n // Session: read current claims via bearer token or cookie\n router.get(mePath, async (req: any, res: any) => {\n try {\n const session = opts.session;\n if (!session) {\n res.status(501).json({ authenticated: false, code: 'sessions_disabled', message: 'Sessions are not configured' });\n return;\n }\n const parsed = await session.parse(req.headers || {});\n if (!parsed.ok) {\n res.status(401).json({ authenticated: false, code: 'unauthorized', message: 'No valid session' });\n return;\n }\n res.status(200).json({ authenticated: true, claims: (parsed as any).claims });\n } catch (e: any) {\n res.status(500).json({ authenticated: false, code: 'internal', message: e?.message || 'Internal error' });\n }\n });\n\n // Session: logout clears cookie (best-effort)\n router.post(logoutPath, async (_req: any, res: any) => {\n try {\n const session = opts.session;\n if (session) res.set('Set-Cookie', session.buildClearCookie());\n res.status(200).json({ success: true });\n } catch (e: any) {\n res.status(500).json({ success: false, error: 'internal', details: e?.message });\n }\n });\n\n // Session: refresh (sliding expiration)\n router.post('/session/refresh', async (req: any, res: any) => {\n try {\n const sessionKind = ((req.body || {}).sessionKind === 'cookie') ? 'cookie' : ((req.body || {}).session_kind === 'cookie' ? 'cookie' : 'jwt');\n const session = opts.session;\n if (!session) {\n res.status(501).json({ code: 'sessions_disabled', message: 'Sessions are not configured' });\n return;\n }\n const out = await session.refresh(req.headers || {});\n if (!out.ok || !out.jwt) {\n const code = out.code || 'not_eligible';\n const message = out.message || 'Refresh not eligible';\n res.status(code === 'unauthorized' ? 401 : 400).json({ code, message });\n return;\n }\n if (sessionKind === 'cookie') {\n res.set('Set-Cookie', session.buildSetCookie(out.jwt));\n res.status(200).json({ ok: true });\n } else {\n res.status(200).json({ ok: true, jwt: out.jwt });\n }\n } catch (e: any) {\n res.status(500).json({ code: 'internal', message: e?.message || 'Internal error' });\n }\n });\n\n // Email recovery hook (DKIM/TEE flow):\n // Accept a ForwardableEmailPayload from the email worker and call the\n // per-user email-recoverer contract deployed on `accountId`.\n router.post('/recover-email', async (req: any, res: any) => {\n try {\n const prefer = String(req?.headers?.prefer || '').toLowerCase();\n const respondAsync =\n prefer.includes('respond-async') ||\n String((req?.query as any)?.async || '').trim() === '1' ||\n String((req?.query as any)?.respond_async || '').trim() === '1';\n\n const parsed = parseRecoverEmailRequest(req.body as unknown, { headers: req.headers as any });\n if (!parsed.ok) {\n res.status(parsed.status).json({ code: parsed.code, message: parsed.message });\n return;\n }\n const { accountId, emailBlob, explicitMode } = parsed;\n\n if (!service.emailRecovery) {\n res.status(503).json({ code: 'email_recovery_unavailable', message: 'EmailRecoveryService is not configured on this server' });\n return;\n }\n\n if (respondAsync) {\n void service.emailRecovery\n .requestEmailRecovery({ accountId, emailBlob, explicitMode })\n .then((result) => {\n logger.info('[recover-email] async complete', {\n success: result?.success === true,\n accountId,\n error: result?.success ? undefined : result?.error,\n });\n })\n .catch((err: any) => {\n logger.error('[recover-email] async error', {\n accountId,\n error: err?.message || String(err),\n });\n });\n\n res.status(202).json({ success: true, queued: true, accountId });\n return;\n }\n\n const result = await service.emailRecovery.requestEmailRecovery({ accountId, emailBlob, explicitMode });\n res.status(result.success ? 202 : 400).json(result);\n } catch (e: any) {\n res.status(500).json({ code: 'internal', message: e?.message || 'Internal error' });\n }\n });\n\n router.get('/shamir/key-info', async (_req: any, res: any) => {\n const shamir = service.shamirService;\n if (!shamir || !shamir.hasShamir()) {\n return res.status(503).json({ error: 'shamir_disabled', message: 'Shamir 3-pass is not configured on this server' });\n }\n try {\n const serverResponse = await handleGetShamirKeyInfo(shamir);\n Object.entries(serverResponse.headers).forEach(([k, v]) => res.set(k, v as any));\n res.status(serverResponse.status);\n res.send(JSON.parse(serverResponse.body));\n } catch (e: any) {\n res.status(500).json({ error: 'internal', details: e?.message });\n }\n });\n\n if (opts.healthz) {\n router.get('/healthz', async (_req: Request, res: Response) => {\n const shamir = service.shamirService;\n const shamirConfigured = Boolean(shamir && shamir.hasShamir());\n let currentKeyId: string | null = null;\n if (shamirConfigured && shamir) {\n try {\n const payload = JSON.parse((await handleGetShamirKeyInfo(shamir)).body) as { currentKeyId?: string };\n currentKeyId = payload.currentKeyId || null;\n } catch {}\n }\n\n const proverBaseUrl = service.emailRecovery?.getZkEmailProverBaseUrl?.() ?? null;\n const zkEmailConfigured = Boolean(proverBaseUrl);\n\n res.status(200).json({\n ok: true,\n // Backwards-compatible field (was previously top-level).\n currentKeyId,\n shamir: { configured: shamirConfigured, currentKeyId },\n zkEmail: { configured: zkEmailConfigured, proverBaseUrl },\n });\n });\n }\n\n if (opts.readyz) {\n router.get('/readyz', async (_req: Request, res: Response) => {\n const shamir = service.shamirService;\n const shamirConfigured = Boolean(shamir && shamir.hasShamir());\n\n let shamirReady: boolean | null = null;\n let shamirCurrentKeyId: string | null = null;\n let shamirError: string | undefined;\n if (shamirConfigured && shamir) {\n try {\n await shamir.ensureReady();\n shamirReady = true;\n const payload = JSON.parse((await handleGetShamirKeyInfo(shamir)).body) as { currentKeyId?: string };\n shamirCurrentKeyId = payload.currentKeyId || null;\n } catch (e: any) {\n shamirReady = false;\n shamirError = e?.message || String(e);\n }\n }\n\n const zk = service.emailRecovery\n ? await service.emailRecovery.checkZkEmailProverHealth()\n : { configured: false, baseUrl: null, healthy: null as boolean | null };\n\n const ok =\n (shamirConfigured ? shamirReady === true : true) &&\n (zk.configured ? zk.healthy === true : true);\n\n res.status(ok ? 200 : 503).json({\n ok,\n shamir: {\n configured: shamirConfigured,\n ready: shamirConfigured ? shamirReady : null,\n currentKeyId: shamirCurrentKeyId,\n error: shamirError,\n },\n zkEmail: zk,\n });\n });\n }\n\n // ROR manifest for Related Origin Requests (wallet-scoped credentials)\n const wellKnownPaths = ['/.well-known/webauthn', '/.well-known/webauthn/'];\n for (const p of wellKnownPaths) {\n router.get(p, async (_req: Request, res: Response) => {\n try {\n const origins = await service.getRorOrigins();\n res.set('Content-Type', 'application/json; charset=utf-8');\n // Short TTL + SWR so updates propagate while staying cache-friendly\n res.set('Cache-Control', 'max-age=60, stale-while-revalidate=600');\n res.status(200).send(JSON.stringify({ origins }));\n } catch (e: any) {\n res.status(200).json({ origins: [] });\n }\n });\n }\n\n return router;\n}\n\nexport interface KeyRotationCronOptions {\n enabled?: boolean;\n intervalMinutes?: number;\n maxGraceKeys?: number;\n logger?: RouterLogger | null;\n}\n\nexport function startKeyRotationCronjob(\n service: AuthService,\n opts: KeyRotationCronOptions = {},\n): { stop(): void } {\n const logger = normalizeRouterLogger(opts.logger);\n const enabled = Boolean(opts.enabled);\n const intervalMinutes = Math.max(1, Number(opts.intervalMinutes || 0) || 60);\n const maxGraceKeys = Math.max(0, Number(opts.maxGraceKeys ?? 0) || 0);\n\n let timer: any = null;\n let inFlight = false;\n\n const run = async () => {\n if (!enabled) return;\n if (inFlight) {\n logger.warn('[key-rotation-cron] previous rotation still running; skipping');\n return;\n }\n inFlight = true;\n try {\n const shamir = service.shamirService;\n if (!shamir || !shamir.hasShamir()) {\n logger.warn('[key-rotation-cron] Shamir not configured; skipping rotation');\n return;\n }\n\n const rotation = await shamir.rotateShamirServerKeypair();\n logger.info('[key-rotation-cron] rotated key', {\n newKeyId: rotation.newKeyId,\n previousKeyId: rotation.previousKeyId,\n graceKeyIds: rotation.graceKeyIds,\n });\n\n if (maxGraceKeys > 0) {\n const graceKeyIds = shamir.getGraceKeyIds();\n if (graceKeyIds.length > maxGraceKeys) {\n const toRemove = graceKeyIds.slice(0, graceKeyIds.length - maxGraceKeys);\n for (const keyId of toRemove) {\n try {\n const removed = await shamir.removeGraceKeyInternal(keyId, { persist: true });\n logger.info('[key-rotation-cron] pruned grace key', { keyId, removed });\n } catch (e: any) {\n logger.warn('[key-rotation-cron] failed to prune grace key', { keyId, error: e?.message || String(e) });\n }\n }\n }\n }\n } catch (e: any) {\n logger.error('[key-rotation-cron] rotation failed', { error: e?.message || String(e) });\n } finally {\n inFlight = false;\n }\n };\n\n if (enabled) {\n timer = setInterval(() => { void run(); }, intervalMinutes * 60_000);\n }\n\n return {\n stop() {\n if (timer) clearInterval(timer);\n timer = null;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -16,9 +16,7 @@ const PASSKEY_MANAGER_DEFAULT_CONFIGS = {
|
|
|
16
16
|
pollingIntervalMs: 4e3,
|
|
17
17
|
maxPollingDurationMs: 1800 * 1e3,
|
|
18
18
|
pendingTtlMs: 1800 * 1e3,
|
|
19
|
-
mailtoAddress: "recover@web3authn.org"
|
|
20
|
-
dkimVerifierAccountId: "email-dkim-verifier-v1.testnet",
|
|
21
|
-
verificationViewMethod: "get_verification_result"
|
|
19
|
+
mailtoAddress: "recover@web3authn.org"
|
|
22
20
|
}
|
|
23
21
|
},
|
|
24
22
|
vrfWorkerConfigs: { shamir3pass: {
|
|
@@ -40,7 +38,7 @@ const PASSKEY_MANAGER_DEFAULT_CONFIGS = {
|
|
|
40
38
|
}
|
|
41
39
|
};
|
|
42
40
|
const DEFAULT_EMAIL_RECOVERY_CONTRACTS = {
|
|
43
|
-
emailRecovererGlobalContract: PASSKEY_MANAGER_DEFAULT_CONFIGS.emailRecoveryContracts.
|
|
41
|
+
emailRecovererGlobalContract: PASSKEY_MANAGER_DEFAULT_CONFIGS.emailRecoveryContracts.emailRecovererGlobalContract,
|
|
44
42
|
zkEmailVerifierContract: PASSKEY_MANAGER_DEFAULT_CONFIGS.emailRecoveryContracts.zkEmailVerifierContract,
|
|
45
43
|
emailDkimVerifierContract: PASSKEY_MANAGER_DEFAULT_CONFIGS.emailRecoveryContracts.emailDkimVerifierContract
|
|
46
44
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"defaultConfigs.js","names":["PASSKEY_MANAGER_DEFAULT_CONFIGS: TatchiConfigs","DEFAULT_EMAIL_RECOVERY_CONTRACTS: EmailRecoveryContracts"],"sources":["../../../../../../src/core/defaultConfigs.ts"],"sourcesContent":["import type { EmailRecoveryContracts, TatchiConfigs, TatchiConfigsInput } from './types/tatchi';\n\n// Default SDK configs suitable for local dev.\n// Cross-origin wallet isolation is recommended; set iframeWallet in your app config when you have a dedicated origin.\n// Consumers can shallow-merge overrides by field.\n\nexport const PASSKEY_MANAGER_DEFAULT_CONFIGS: TatchiConfigs = {\n // You can provide a single URL or a comma-separated list for failover.\n // First URL is treated as primary, subsequent URLs are fallbacks.\n nearRpcUrl: 'https://test.rpc.fastnear.com, https://rpc.testnet.near.org',\n nearNetwork: 'testnet',\n contractId: 'w3a-v1.testnet',\n nearExplorerUrl: 'https://testnet.nearblocks.io',\n // Warm signing session defaults used by login/unlock flows.\n // Enforcement (TTL/uses) is owned by the VRF worker; signer workers remain one-shot.\n signingSessionDefaults: {\n ttlMs: 0, // 0 minutes\n remainingUses: 0, // default to requiring a touchID prompt for each transaction\n },\n relayer: {\n // accountId: 'w3a-v1.testnet',\n // No default relayer URL. Force apps to configure via env/overrides.\n // Using an empty string triggers early validation errors in code paths that require it.\n url: '',\n delegateActionRoute: '/signed-delegate',\n emailRecovery: {\n // Require at least 0.01 NEAR available to start email recovery.\n minBalanceYocto: '10000000000000000000000', // 0.01 NEAR\n // Poll every 4 seconds for verification status / access key.\n pollingIntervalMs: 4000,\n // Stop polling after 30 minutes.\n maxPollingDurationMs: 30 * 60 * 1000,\n // Expire pending recovery records after 30 minutes.\n pendingTtlMs: 30 * 60 * 1000,\n // Default recovery mailbox for examples / docs.\n mailtoAddress: 'recover@web3authn.org',\n
|
|
1
|
+
{"version":3,"file":"defaultConfigs.js","names":["PASSKEY_MANAGER_DEFAULT_CONFIGS: TatchiConfigs","DEFAULT_EMAIL_RECOVERY_CONTRACTS: EmailRecoveryContracts"],"sources":["../../../../../../src/core/defaultConfigs.ts"],"sourcesContent":["import type { EmailRecoveryContracts, TatchiConfigs, TatchiConfigsInput } from './types/tatchi';\n\n// Default SDK configs suitable for local dev.\n// Cross-origin wallet isolation is recommended; set iframeWallet in your app config when you have a dedicated origin.\n// Consumers can shallow-merge overrides by field.\n\nexport const PASSKEY_MANAGER_DEFAULT_CONFIGS: TatchiConfigs = {\n // You can provide a single URL or a comma-separated list for failover.\n // First URL is treated as primary, subsequent URLs are fallbacks.\n nearRpcUrl: 'https://test.rpc.fastnear.com, https://rpc.testnet.near.org',\n nearNetwork: 'testnet',\n contractId: 'w3a-v1.testnet',\n nearExplorerUrl: 'https://testnet.nearblocks.io',\n // Warm signing session defaults used by login/unlock flows.\n // Enforcement (TTL/uses) is owned by the VRF worker; signer workers remain one-shot.\n signingSessionDefaults: {\n ttlMs: 0, // 0 minutes\n remainingUses: 0, // default to requiring a touchID prompt for each transaction\n },\n relayer: {\n // accountId: 'w3a-v1.testnet',\n // No default relayer URL. Force apps to configure via env/overrides.\n // Using an empty string triggers early validation errors in code paths that require it.\n url: '',\n delegateActionRoute: '/signed-delegate',\n emailRecovery: {\n // Require at least 0.01 NEAR available to start email recovery.\n minBalanceYocto: '10000000000000000000000', // 0.01 NEAR\n // Poll every 4 seconds for verification status / access key.\n pollingIntervalMs: 4000,\n // Stop polling after 30 minutes.\n maxPollingDurationMs: 30 * 60 * 1000,\n // Expire pending recovery records after 30 minutes.\n pendingTtlMs: 30 * 60 * 1000,\n // Default recovery mailbox for examples / docs.\n mailtoAddress: 'recover@web3authn.org',\n },\n },\n vrfWorkerConfigs: {\n shamir3pass: {\n // default Shamir's P in vrf-wasm-worker, needs to match relay server's Shamir P\n p: '3N5w46AIGjGT2v5Vua_TMD5Ywfa9U2F7-WzW8SNDsIM',\n // No default relay server URL to avoid accidental localhost usage in non-dev envs\n // Defaults to relayer.url when undefined\n relayServerUrl: '',\n applyServerLockRoute: '/vrf/apply-server-lock',\n removeServerLockRoute: '/vrf/remove-server-lock',\n }\n },\n emailRecoveryContracts: {\n emailRecovererGlobalContract: 'w3a-email-recoverer-v1.testnet',\n zkEmailVerifierContract: 'zk-email-verifier-v1.testnet',\n emailDkimVerifierContract: 'email-dkim-verifier-v1.testnet',\n },\n // Configure iframeWallet in application code to point at your dedicated wallet origin when available.\n iframeWallet: {\n walletOrigin: 'https://wallet.example.localhost',\n walletServicePath: '/wallet-service',\n sdkBasePath: '/sdk',\n rpIdOverride: 'example.localhost',\n }\n};\n\nexport const DEFAULT_EMAIL_RECOVERY_CONTRACTS: EmailRecoveryContracts = {\n emailRecovererGlobalContract: PASSKEY_MANAGER_DEFAULT_CONFIGS.emailRecoveryContracts.emailRecovererGlobalContract,\n zkEmailVerifierContract: PASSKEY_MANAGER_DEFAULT_CONFIGS.emailRecoveryContracts.zkEmailVerifierContract,\n emailDkimVerifierContract: PASSKEY_MANAGER_DEFAULT_CONFIGS.emailRecoveryContracts.emailDkimVerifierContract,\n};\n\n// Merge defaults with overrides\nexport function buildConfigsFromEnv(overrides: TatchiConfigsInput = {}): TatchiConfigs {\n\n const defaults = PASSKEY_MANAGER_DEFAULT_CONFIGS;\n const relayerUrl = overrides.relayer?.url ?? defaults.relayer?.url ?? '';\n // Prefer explicit override for relayer URL; fall back to default preset.\n // Used below to default VRF relayServerUrl when it is undefined.\n const relayServerUrlDefault = relayerUrl;\n\n const merged: TatchiConfigs = {\n nearRpcUrl: overrides.nearRpcUrl ?? defaults.nearRpcUrl,\n nearNetwork: overrides.nearNetwork ?? defaults.nearNetwork,\n contractId: overrides.contractId ?? defaults.contractId,\n nearExplorerUrl: overrides.nearExplorerUrl ?? defaults.nearExplorerUrl,\n walletTheme: overrides.walletTheme ?? defaults.walletTheme,\n signingSessionDefaults: {\n ttlMs: overrides.signingSessionDefaults?.ttlMs\n ?? defaults.signingSessionDefaults?.ttlMs,\n remainingUses: overrides.signingSessionDefaults?.remainingUses\n ?? defaults.signingSessionDefaults?.remainingUses,\n },\n relayer: {\n url: relayerUrl,\n delegateActionRoute: overrides.relayer?.delegateActionRoute\n ?? defaults.relayer?.delegateActionRoute,\n emailRecovery: {\n minBalanceYocto: overrides.relayer?.emailRecovery?.minBalanceYocto\n ?? defaults.relayer?.emailRecovery?.minBalanceYocto,\n pollingIntervalMs: overrides.relayer?.emailRecovery?.pollingIntervalMs\n ?? defaults.relayer?.emailRecovery?.pollingIntervalMs,\n maxPollingDurationMs: overrides.relayer?.emailRecovery?.maxPollingDurationMs\n ?? defaults.relayer?.emailRecovery?.maxPollingDurationMs,\n pendingTtlMs: overrides.relayer?.emailRecovery?.pendingTtlMs\n ?? defaults.relayer?.emailRecovery?.pendingTtlMs,\n mailtoAddress: overrides.relayer?.emailRecovery?.mailtoAddress\n ?? defaults.relayer?.emailRecovery?.mailtoAddress,\n },\n },\n authenticatorOptions: overrides.authenticatorOptions ?? defaults.authenticatorOptions,\n vrfWorkerConfigs: {\n shamir3pass: {\n p: overrides.vrfWorkerConfigs?.shamir3pass?.p\n ?? defaults.vrfWorkerConfigs?.shamir3pass?.p,\n relayServerUrl: overrides.vrfWorkerConfigs?.shamir3pass?.relayServerUrl\n ?? defaults.vrfWorkerConfigs?.shamir3pass?.relayServerUrl\n ?? relayServerUrlDefault,\n applyServerLockRoute: overrides.vrfWorkerConfigs?.shamir3pass?.applyServerLockRoute\n ?? defaults.vrfWorkerConfigs?.shamir3pass?.applyServerLockRoute,\n removeServerLockRoute: overrides.vrfWorkerConfigs?.shamir3pass?.removeServerLockRoute\n ?? defaults.vrfWorkerConfigs?.shamir3pass?.removeServerLockRoute,\n },\n },\n emailRecoveryContracts: {\n emailRecovererGlobalContract: overrides.emailRecoveryContracts?.emailRecovererGlobalContract\n ?? defaults.emailRecoveryContracts?.emailRecovererGlobalContract,\n zkEmailVerifierContract: overrides.emailRecoveryContracts?.zkEmailVerifierContract\n ?? defaults.emailRecoveryContracts?.zkEmailVerifierContract,\n emailDkimVerifierContract: overrides.emailRecoveryContracts?.emailDkimVerifierContract\n ?? defaults.emailRecoveryContracts?.emailDkimVerifierContract,\n },\n iframeWallet: {\n walletOrigin: overrides.iframeWallet?.walletOrigin\n ?? defaults.iframeWallet?.walletOrigin,\n walletServicePath: overrides.iframeWallet?.walletServicePath\n ?? defaults.iframeWallet?.walletServicePath\n ?? '/wallet-service',\n sdkBasePath: overrides.iframeWallet?.sdkBasePath\n ?? defaults.iframeWallet?.sdkBasePath\n ?? '/sdk',\n rpIdOverride: overrides.iframeWallet?.rpIdOverride\n ?? defaults.iframeWallet?.rpIdOverride,\n }\n };\n if (!merged.contractId) {\n throw new Error('[configPresets] Missing required config: contractId');\n }\n if (!merged.relayer.url) {\n throw new Error('[configPresets] Missing required config: relayer.url');\n }\n return merged;\n}\n"],"mappings":";AAMA,MAAaA,kCAAiD;CAG5D,YAAY;CACZ,aAAa;CACb,YAAY;CACZ,iBAAiB;CAGjB,wBAAwB;EACtB,OAAO;EACP,eAAe;;CAEjB,SAAS;EAIP,KAAK;EACL,qBAAqB;EACrB,eAAe;GAEb,iBAAiB;GAEjB,mBAAmB;GAEnB,sBAAsB,OAAU;GAEhC,cAAc,OAAU;GAExB,eAAe;;;CAGnB,kBAAkB,EAChB,aAAa;EAEX,GAAG;EAGH,gBAAgB;EAChB,sBAAsB;EACtB,uBAAuB;;CAG3B,wBAAwB;EACtB,8BAA8B;EAC9B,yBAAyB;EACzB,2BAA2B;;CAG7B,cAAc;EACZ,cAAc;EACd,mBAAmB;EACnB,aAAa;EACb,cAAc;;;AAIlB,MAAaC,mCAA2D;CACtE,8BAA8B,gCAAgC,uBAAuB;CACrF,yBAAyB,gCAAgC,uBAAuB;CAChF,2BAA2B,gCAAgC,uBAAuB"}
|
|
@@ -2,19 +2,37 @@ import bs58 from "bs58";
|
|
|
2
2
|
import "@noble/ed25519";
|
|
3
3
|
|
|
4
4
|
//#region src/core/nearCrypto.ts
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
const NEAR_ED25519_KEY_PREFIX = "ed25519:";
|
|
6
|
+
/**
|
|
7
|
+
* Ensure a key string has the NEAR Ed25519 prefix (`ed25519:`).
|
|
8
|
+
*
|
|
9
|
+
* - Accepts either `ed25519:<base58>` or a bare `<base58>` string.
|
|
10
|
+
* - Canonicalizes `ED25519:` → `ed25519:`.
|
|
11
|
+
* - If a different prefix is present (e.g. `secp256k1:`), returns the input unchanged.
|
|
12
|
+
*/
|
|
13
|
+
function ensureEd25519Prefix(value) {
|
|
14
|
+
const raw = String(value || "").trim();
|
|
15
|
+
if (!raw) return "";
|
|
16
|
+
if (/^[a-z0-9_]+:/i.test(raw)) {
|
|
17
|
+
if (/^ed25519:/i.test(raw)) return `${NEAR_ED25519_KEY_PREFIX}${raw.replace(/^ed25519:/i, "")}`;
|
|
18
|
+
return raw;
|
|
19
|
+
}
|
|
20
|
+
return `${NEAR_ED25519_KEY_PREFIX}${raw}`;
|
|
8
21
|
}
|
|
9
|
-
|
|
10
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Remove the NEAR Ed25519 prefix (`ed25519:`) if present.
|
|
24
|
+
* Useful for comparing keys where one side may omit the prefix.
|
|
25
|
+
*/
|
|
26
|
+
function stripEd25519Prefix(value) {
|
|
27
|
+
const raw = String(value || "").trim();
|
|
28
|
+
return raw.replace(/^ed25519:/i, "");
|
|
11
29
|
}
|
|
12
30
|
/**
|
|
13
31
|
* Parse NEAR secret key string ('ed25519:...') into its components.
|
|
14
32
|
* NEAR secrets are 64 bytes: seed(32) | pub(32)
|
|
15
33
|
*/
|
|
16
34
|
function parseNearSecretKey(str) {
|
|
17
|
-
const b58 =
|
|
35
|
+
const b58 = stripEd25519Prefix(str);
|
|
18
36
|
const bytes = bs58.decode(b58);
|
|
19
37
|
if (bytes.length !== 64) throw new Error(`Invalid NEAR secret key length: ${bytes.length}`);
|
|
20
38
|
const all = new Uint8Array(bytes);
|
|
@@ -28,9 +46,9 @@ function parseNearSecretKey(str) {
|
|
|
28
46
|
/** Convert raw 32-byte public key to NEAR string ('ed25519:...') */
|
|
29
47
|
function toPublicKeyString(pub) {
|
|
30
48
|
if (!(pub?.length === 32)) throw new Error("Public key must be 32 bytes");
|
|
31
|
-
return
|
|
49
|
+
return ensureEd25519Prefix(bs58.encode(pub));
|
|
32
50
|
}
|
|
33
51
|
|
|
34
52
|
//#endregion
|
|
35
|
-
export { parseNearSecretKey, toPublicKeyString };
|
|
53
|
+
export { ensureEd25519Prefix, parseNearSecretKey, toPublicKeyString };
|
|
36
54
|
//# sourceMappingURL=nearCrypto.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nearCrypto.js","names":[],"sources":["../../../../../../src/core/nearCrypto.ts"],"sourcesContent":["import * as ed25519 from '@noble/ed25519';\nimport bs58 from 'bs58';\n\
|
|
1
|
+
{"version":3,"file":"nearCrypto.js","names":[],"sources":["../../../../../../src/core/nearCrypto.ts"],"sourcesContent":["import * as ed25519 from '@noble/ed25519';\nimport bs58 from 'bs58';\n\nexport const NEAR_ED25519_KEY_PREFIX = 'ed25519:' as const;\n\n/**\n * Ensure a key string has the NEAR Ed25519 prefix (`ed25519:`).\n *\n * - Accepts either `ed25519:<base58>` or a bare `<base58>` string.\n * - Canonicalizes `ED25519:` → `ed25519:`.\n * - If a different prefix is present (e.g. `secp256k1:`), returns the input unchanged.\n */\nexport function ensureEd25519Prefix(value: string): string {\n const raw = String(value || '').trim();\n if (!raw) return '';\n\n // If it already looks like \"<prefix>:...\", only normalize when the prefix is ed25519.\n if (/^[a-z0-9_]+:/i.test(raw)) {\n if (/^ed25519:/i.test(raw)) {\n return `${NEAR_ED25519_KEY_PREFIX}${raw.replace(/^ed25519:/i, '')}`;\n }\n return raw;\n }\n\n return `${NEAR_ED25519_KEY_PREFIX}${raw}`;\n}\n\n/**\n * Remove the NEAR Ed25519 prefix (`ed25519:`) if present.\n * Useful for comparing keys where one side may omit the prefix.\n */\nexport function stripEd25519Prefix(value: string): string {\n const raw = String(value || '').trim();\n return raw.replace(/^ed25519:/i, '');\n}\n\n/**\n * Creates a NEAR-compatible Ed25519 keypair formatted as strings:\n * - publicKey: 'ed25519:' + base58(pub)\n * - privateKey: 'ed25519:' + base58(seed(32) | pub(32))\n */\nexport async function createNearKeypair(): Promise<{ publicKey: string; privateKey: string }> {\n const seed = ed25519.utils.randomPrivateKey(); // 32 bytes\n const pub = await ed25519.getPublicKeyAsync(seed); // 32 bytes\n\n const secret = new Uint8Array(64);\n secret.set(seed, 0);\n secret.set(pub, 32);\n\n const publicKey = ensureEd25519Prefix(bs58.encode(pub));\n const privateKey = ensureEd25519Prefix(bs58.encode(secret));\n return { publicKey, privateKey };\n}\n\n/** Parse NEAR public key string ('ed25519:...') into 32-byte Uint8Array */\nexport function parseNearPublicKey(str: string): Uint8Array {\n const b58 = stripEd25519Prefix(str);\n const bytes = bs58.decode(b58);\n if (bytes.length !== 32) {\n throw new Error(`Invalid NEAR public key length: ${bytes.length}`);\n }\n return new Uint8Array(bytes);\n}\n\n/**\n * Parse NEAR secret key string ('ed25519:...') into its components.\n * NEAR secrets are 64 bytes: seed(32) | pub(32)\n */\nexport function parseNearSecretKey(str: string): { seed: Uint8Array; pub: Uint8Array } {\n const b58 = stripEd25519Prefix(str);\n const bytes = bs58.decode(b58);\n if (bytes.length !== 64) {\n throw new Error(`Invalid NEAR secret key length: ${bytes.length}`);\n }\n const all = new Uint8Array(bytes);\n const seed = all.slice(0, 32);\n const pub = all.slice(32, 64);\n return { seed, pub };\n}\n\n/** Convert raw 32-byte public key to NEAR string ('ed25519:...') */\nexport function toPublicKeyString(pub: Uint8Array): string {\n if (!(pub?.length === 32)) {\n throw new Error('Public key must be 32 bytes');\n }\n return ensureEd25519Prefix(bs58.encode(pub));\n}\n\n/** Convert raw seed(32) + pub(32) to NEAR secret string ('ed25519:...') */\nexport function toSecretKeyString(seed: Uint8Array, pub: Uint8Array): string {\n if (!(seed?.length === 32)) {\n throw new Error('Seed must be 32 bytes');\n }\n if (!(pub?.length === 32)) {\n throw new Error('Public key must be 32 bytes');\n }\n const secret = new Uint8Array(64);\n secret.set(seed, 0);\n secret.set(pub, 32);\n return ensureEd25519Prefix(bs58.encode(secret));\n}\n"],"mappings":";;;;AAGA,MAAa,0BAA0B;;;;;;;;AASvC,SAAgB,oBAAoB,OAAuB;CACzD,MAAM,MAAM,OAAO,SAAS,IAAI;AAChC,KAAI,CAAC,IAAK,QAAO;AAGjB,KAAI,gBAAgB,KAAK,MAAM;AAC7B,MAAI,aAAa,KAAK,KACpB,QAAO,GAAG,0BAA0B,IAAI,QAAQ,cAAc;AAEhE,SAAO;;AAGT,QAAO,GAAG,0BAA0B;;;;;;AAOtC,SAAgB,mBAAmB,OAAuB;CACxD,MAAM,MAAM,OAAO,SAAS,IAAI;AAChC,QAAO,IAAI,QAAQ,cAAc;;;;;;AAmCnC,SAAgB,mBAAmB,KAAoD;CACrF,MAAM,MAAM,mBAAmB;CAC/B,MAAM,QAAQ,KAAK,OAAO;AAC1B,KAAI,MAAM,WAAW,GACnB,OAAM,IAAI,MAAM,mCAAmC,MAAM;CAE3D,MAAM,MAAM,IAAI,WAAW;CAC3B,MAAM,OAAO,IAAI,MAAM,GAAG;CAC1B,MAAM,MAAM,IAAI,MAAM,IAAI;AAC1B,QAAO;EAAE;EAAM;;;;AAIjB,SAAgB,kBAAkB,KAAyB;AACzD,KAAI,EAAE,KAAK,WAAW,IACpB,OAAM,IAAI,MAAM;AAElB,QAAO,oBAAoB,KAAK,OAAO"}
|
|
Binary file
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import type { AccountId } from '../types/accountIds';
|
|
2
2
|
import { type RecoveryEmailRecord } from '../IndexedDBManager';
|
|
3
|
+
import type { FinalExecutionOutcome } from '@near-js/types';
|
|
3
4
|
export { EmailRecoveryPendingStore, type PendingStore } from './emailRecoveryPendingStore';
|
|
4
5
|
export type RecoveryEmailEntry = {
|
|
5
6
|
hashHex: string;
|
|
6
7
|
email: string;
|
|
7
8
|
};
|
|
8
9
|
export { type RecoveryEmailRecord };
|
|
10
|
+
export type LinkDeviceRegisterUserResponse = {
|
|
11
|
+
verified?: boolean;
|
|
12
|
+
registration_info?: unknown;
|
|
13
|
+
registrationInfo?: unknown;
|
|
14
|
+
error?: unknown;
|
|
15
|
+
};
|
|
16
|
+
export declare function parseLinkDeviceRegisterUserResponse(outcome: FinalExecutionOutcome): LinkDeviceRegisterUserResponse | null;
|
|
9
17
|
export declare const canonicalizeEmail: (email: string) => string;
|
|
10
18
|
export declare const bytesToHex: (bytes: number[] | Uint8Array) => string;
|
|
11
19
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/EmailRecovery/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3F,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,OAAO,EAAE,KAAK,mBAAmB,EAAE,CAAC;AAEpC,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,MA2BjD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,EAAE,GAAG,UAAU,KAAG,MAKzD,CAAC;AA4BF;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACvG,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACnB,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B,CAAC,CAqBD;AAED,wBAAsB,sBAAsB,CAAC,aAAa,EAAE,SAAS,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAErG"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/core/EmailRecovery/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAoB,KAAK,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AACjF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,EAAE,yBAAyB,EAAE,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE3F,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,OAAO,EAAE,KAAK,mBAAmB,EAAE,CAAC;AAEpC,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAUF,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,qBAAqB,GAC7B,8BAA8B,GAAG,IAAI,CAgBvC;AAED,eAAO,MAAM,iBAAiB,GAAI,OAAO,MAAM,KAAG,MA2BjD,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,EAAE,GAAG,UAAU,KAAG,MAKzD,CAAC;AA4BF;;;GAGG;AACH,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACvG,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACnB,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B,CAAC,CAqBD;AAED,wBAAsB,sBAAsB,CAAC,aAAa,EAAE,SAAS,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAErG"}
|
|
@@ -8,7 +8,7 @@ import { type PendingStore } from '../EmailRecovery';
|
|
|
8
8
|
export type PendingEmailRecoveryStatus = 'awaiting-email' | 'awaiting-add-key' | 'finalizing' | 'complete' | 'error';
|
|
9
9
|
export type PendingEmailRecovery = {
|
|
10
10
|
accountId: AccountId;
|
|
11
|
-
recoveryEmail
|
|
11
|
+
recoveryEmail?: string;
|
|
12
12
|
deviceNumber: number;
|
|
13
13
|
nearPublicKey: string;
|
|
14
14
|
requestId: string;
|
|
@@ -54,13 +54,14 @@ export declare class EmailRecoveryFlow {
|
|
|
54
54
|
private toBigInt;
|
|
55
55
|
private computeAvailableBalance;
|
|
56
56
|
private assertSufficientBalance;
|
|
57
|
-
private
|
|
57
|
+
private getCanonicalRecoveryEmail;
|
|
58
58
|
private getNextDeviceNumberFromContract;
|
|
59
59
|
private collectRecoveryCredentialOrFail;
|
|
60
60
|
private deriveRecoveryKeysOrFail;
|
|
61
61
|
private emitAwaitEmail;
|
|
62
62
|
private emitAutoLoginEvent;
|
|
63
|
-
private
|
|
63
|
+
private checkViaEmailRecovererAttempt;
|
|
64
|
+
private isRecoveryAccessKeyPresent;
|
|
64
65
|
private buildPollingEventData;
|
|
65
66
|
private sleepForPollInterval;
|
|
66
67
|
private pollUntil;
|
|
@@ -78,7 +79,7 @@ export declare class EmailRecoveryFlow {
|
|
|
78
79
|
}): Promise<string>;
|
|
79
80
|
start(args: {
|
|
80
81
|
accountId: string;
|
|
81
|
-
recoveryEmail
|
|
82
|
+
recoveryEmail?: string;
|
|
82
83
|
}): Promise<{
|
|
83
84
|
mailtoUrl: string;
|
|
84
85
|
nearPublicKey: string;
|
|
@@ -105,8 +106,10 @@ export declare class EmailRecoveryFlow {
|
|
|
105
106
|
}): Promise<void>;
|
|
106
107
|
private pollUntilAddKey;
|
|
107
108
|
private initializeNonceManager;
|
|
108
|
-
private
|
|
109
|
+
private signNewDevice2RegistrationTx;
|
|
109
110
|
private broadcastRegistrationTxAndWaitFinal;
|
|
111
|
+
private getTxHash;
|
|
112
|
+
private extractNearExecutionLogs;
|
|
110
113
|
private mapAuthenticatorsFromContract;
|
|
111
114
|
private syncAuthenticatorsBestEffort;
|
|
112
115
|
private setLastUserBestEffort;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emailRecovery.d.ts","sourceRoot":"","sources":["../../../../../src/core/TatchiPasskey/emailRecovery.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"emailRecovery.d.ts","sourceRoot":"","sources":["../../../../../src/core/TatchiPasskey/emailRecovery.ts"],"names":[],"mappings":"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"}
|