[{"data":1,"prerenderedAt":4170},["ShallowReactive",2],{"navLinks":3,"sidebar_docs_navigation_\u002Fdocs\u002Fiam":64,"navigation":257,"navLinks_footer":837,"\u002Fdocs\u002Fiam\u002Fessentials\u002Fmfa_page":850,"\u002Fdocs\u002Fiam\u002Fessentials\u002Fmfa_surround":2791,"\u002Fdocs\u002Fiam\u002Fessentials\u002Fmfa":2794},{"id":4,"extension":5,"links":6,"meta":61,"stem":62,"__hash__":63},"navigationMenu\u002Fnavigation.json","json",[7,52,57],{"nested":8,"label":9,"icon":10,"to":11,"children":12},true,"Docs","i-lucide-book-open","\u002Fdocs\u002Fgetting-started",[13,19,26,32,39,45],{"label":14,"icon":15,"to":11,"description":16,"github":17,"badge":18},"Getting Started","i-lucide-rocket","An introduction to help you understand the core components.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fdocshub","Start Here",{"label":20,"icon":21,"to":22,"description":23,"github":24,"badge":25},"Auth H3 Client","i-lucide-key-round","\u002Fdocs\u002Fauth-h3client","Seamlessly enforce OAuth 2.0 authentication and session management integrated directly as the client of the IAM module.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fauth-h3client","Core",{"label":27,"icon":28,"to":29,"description":30,"github":31,"badge":25},"IAM","i-lucide-shield-check","\u002Fdocs\u002Fiam","Identity and Access Management featuring granular roles, permissions, and security policies.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fauth",{"label":33,"icon":34,"to":35,"description":36,"github":37,"badge":38},"Bot Detection","i-lucide-cpu","\u002Fdocs\u002Fbot-detection","Advanced behavioral analysis and request fingerprinting to stop malicious automated traffic.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fbot-detector","Security",{"label":40,"icon":41,"to":42,"description":43,"github":44,"badge":38},"Shield Base","i-lucide-database-zap","\u002Fdocs\u002Fshield-base","CLI and programmatic toolkit for compiling offline-ready IP intelligence databases from BGP, GeoIP, Tor, FireHOL, and other public threat feeds.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Fshield-base-cli",{"label":46,"icon":47,"to":48,"description":49,"github":50,"badge":51},"Utils","i-lucide-wrench","\u002Fdocs\u002Futils","A standard library of highly optimized helpers for formatting, validation, and core logic.","https:\u002F\u002Fgithub.com\u002FSergo706\u002Futils","Library",{"nested":53,"label":54,"icon":55,"to":56},false,"Blog","i-lucide-pen-line","\u002Fblog",{"nested":53,"label":58,"icon":59,"to":60},"Website","lucide:app-window-mac","https:\u002F\u002Friavzon.com",{},"navigation","gkaQ0xRGxSLrLyM3kttLe0oBwkrR1EBjlepF8LSbwF8",[65],{"title":9,"path":66,"stem":67,"children":68,"page":53},"\u002Fdocs","docs",[69],{"title":27,"path":29,"stem":70,"children":71},"docs\u002Fiam\u002Findex",[72,73,76,216,219,236,240],{"title":27,"path":29,"stem":70},{"title":14,"path":74,"stem":75},"\u002Fdocs\u002Fiam\u002Fgetting-started","docs\u002Fiam\u002F00.getting-started",{"title":77,"path":78,"stem":79,"children":80},"Essentials","\u002Fdocs\u002Fiam\u002Fessentials","docs\u002Fiam\u002F01.essentials\u002Findex",[81,82,86,90,94,98,102,106,110,114,118,122,126,130,134,138,142,146,150,154,158,162,166],{"title":77,"path":78,"stem":79},{"title":83,"path":84,"stem":85},"Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Ftokens","docs\u002Fiam\u002F01.essentials\u002F00.tokens",{"title":87,"path":88,"stem":89},"Access Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Faccess-tokens","docs\u002Fiam\u002F01.essentials\u002F01.access-tokens",{"title":91,"path":92,"stem":93},"Refresh Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Frefresh-tokens","docs\u002Fiam\u002F01.essentials\u002F02.refresh-tokens",{"title":95,"path":96,"stem":97},"Anomaly Detection","\u002Fdocs\u002Fiam\u002Fessentials\u002Fanomalies","docs\u002Fiam\u002F01.essentials\u002F03.anomalies",{"title":99,"path":100,"stem":101},"Signup","\u002Fdocs\u002Fiam\u002Fessentials\u002Fsignup","docs\u002Fiam\u002F01.essentials\u002F04.signup",{"title":103,"path":104,"stem":105},"Login","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogin","docs\u002Fiam\u002F01.essentials\u002F05.login",{"title":107,"path":108,"stem":109},"Logout","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogout","docs\u002Fiam\u002F01.essentials\u002F06.logout",{"title":111,"path":112,"stem":113},"OAuth","\u002Fdocs\u002Fiam\u002Fessentials\u002Foauth","docs\u002Fiam\u002F01.essentials\u002F07.oauth",{"title":115,"path":116,"stem":117},"Magic Links","\u002Fdocs\u002Fiam\u002Fessentials\u002Fmagic-links","docs\u002Fiam\u002F01.essentials\u002F08.magic-links",{"title":119,"path":120,"stem":121},"Emails","\u002Fdocs\u002Fiam\u002Fessentials\u002Femails","docs\u002Fiam\u002F01.essentials\u002F09.emails",{"title":123,"path":124,"stem":125},"MFA","\u002Fdocs\u002Fiam\u002Fessentials\u002Fmfa","docs\u002Fiam\u002F01.essentials\u002F10.mfa",{"title":127,"path":128,"stem":129},"Fingerprinting","\u002Fdocs\u002Fiam\u002Fessentials\u002Ffingerprinting","docs\u002Fiam\u002F01.essentials\u002F11.fingerprinting",{"title":131,"path":132,"stem":133},"Backend for Frontend","\u002Fdocs\u002Fiam\u002Fessentials\u002Fbff","docs\u002Fiam\u002F01.essentials\u002F12.bff",{"title":135,"path":136,"stem":137},"HMAC Authentication","\u002Fdocs\u002Fiam\u002Fessentials\u002Fhmac","docs\u002Fiam\u002F01.essentials\u002F13.hmac",{"title":139,"path":140,"stem":141},"XSS Protection","\u002Fdocs\u002Fiam\u002Fessentials\u002Fxss","docs\u002Fiam\u002F01.essentials\u002F14.xss",{"title":143,"path":144,"stem":145},"Logging","\u002Fdocs\u002Fiam\u002Fessentials\u002Flogging","docs\u002Fiam\u002F01.essentials\u002F15.logging",{"title":147,"path":148,"stem":149},"Rate Limiting","\u002Fdocs\u002Fiam\u002Fessentials\u002Frate-limiting","docs\u002Fiam\u002F01.essentials\u002F16.rate-limiting",{"title":151,"path":152,"stem":153},"Database","\u002Fdocs\u002Fiam\u002Fessentials\u002Fdatabase","docs\u002Fiam\u002F01.essentials\u002F17.database",{"title":155,"path":156,"stem":157},"Cookies","\u002Fdocs\u002Fiam\u002Fessentials\u002Fcookies","docs\u002Fiam\u002F01.essentials\u002F18.cookies",{"title":159,"path":160,"stem":161},"Service Startup","\u002Fdocs\u002Fiam\u002Fessentials\u002Fservice","docs\u002Fiam\u002F01.essentials\u002F19.service",{"title":163,"path":164,"stem":165},"Password Reset","\u002Fdocs\u002Fiam\u002Fessentials\u002Fpassword-reset","docs\u002Fiam\u002F01.essentials\u002F20.password-reset",{"title":167,"path":168,"stem":169,"children":170},"API Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi","docs\u002Fiam\u002F01.essentials\u002F21.api\u002Findex",[171,172,176,180,210,213],{"title":167,"path":168,"stem":169},{"title":173,"path":174,"stem":175},"Creating Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fcreation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F00.creation",{"title":177,"path":178,"stem":179},"Verifying Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fverification","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F01.verification",{"title":181,"path":182,"stem":183,"children":184},"Manage Tokens","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002Findex",[185,186,190,194,198,202,206],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189},"Privileges","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fprivilege","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F00.privilege",{"title":191,"path":192,"stem":193},"Revocation","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Frevocation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F01.revocation",{"title":195,"path":196,"stem":197},"Rotation","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Frotation","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F02.rotation",{"title":199,"path":200,"stem":201},"IP Restriction","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fip-updates","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F03.ip-updates",{"title":203,"path":204,"stem":205},"Metadata","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Fmetadata","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F04.metadata",{"title":207,"path":208,"stem":209},"Token Listing","\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fmanagement\u002Flist","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F02.management\u002F05.list",{"title":147,"path":211,"stem":212},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Frate-limiting","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F03.rate-limiting",{"title":38,"path":214,"stem":215},"\u002Fdocs\u002Fiam\u002Fessentials\u002Fapi\u002Fsecurity","docs\u002Fiam\u002F01.essentials\u002F21.api\u002F04.security",{"title":38,"path":217,"stem":218},"\u002Fdocs\u002Fiam\u002Fsecurity","docs\u002Fiam\u002F02.security",{"title":220,"path":221,"stem":222,"children":223,"page":53},"Guides","\u002Fdocs\u002Fiam\u002Fguides","docs\u002Fiam\u002F03.guides",[224,228,232],{"title":225,"path":226,"stem":227},"Deployment","\u002Fdocs\u002Fiam\u002Fguides\u002Fdeployment","docs\u002Fiam\u002F03.guides\u002Fdeployment",{"title":229,"path":230,"stem":231},"Operation Scripts","\u002Fdocs\u002Fiam\u002Fguides\u002Foperation-scripts","docs\u002Fiam\u002F03.guides\u002Foperation-scripts",{"title":233,"path":234,"stem":235},"Role-Based Access Control","\u002Fdocs\u002Fiam\u002Fguides\u002Frbac","docs\u002Fiam\u002F03.guides\u002Frbac",{"title":237,"path":238,"stem":239},"Configuration","\u002Fdocs\u002Fiam\u002Fconfiguration","docs\u002Fiam\u002F04.configuration",{"title":241,"path":242,"stem":243,"children":244,"page":53},"Api","\u002Fdocs\u002Fiam\u002Fapi","docs\u002Fiam\u002F05.API",[245,249,253],{"title":246,"path":247,"stem":248},"API Reference","\u002Fdocs\u002Fiam\u002Fapi\u002Fapi","docs\u002Fiam\u002F05.API\u002F00.api",{"title":250,"path":251,"stem":252},"Middleware Reference","\u002Fdocs\u002Fiam\u002Fapi\u002Fmiddlewares","docs\u002Fiam\u002F05.API\u002F02.middlewares",{"title":254,"path":255,"stem":256},"Routes Reference","\u002Fdocs\u002Fiam\u002Fapi\u002Froutes","docs\u002Fiam\u002F05.API\u002F03.routes",[258],{"title":9,"path":66,"stem":67,"children":259,"page":53},[260,398,516,521,577,644],{"title":20,"path":22,"stem":261,"children":262},"docs\u002Fauth-h3client\u002Findex",[263,264,273,307,331,353,356,376,379],{"title":20,"path":22,"stem":261},{"title":14,"path":265,"stem":266,"children":267},"\u002Fdocs\u002Fauth-h3client\u002Fgetting-started","docs\u002Fauth-h3client\u002F00.getting-started\u002Findex",[268,269],{"title":14,"path":265,"stem":266},{"title":270,"path":271,"stem":272},"Nuxt Module","\u002Fdocs\u002Fauth-h3client\u002Fgetting-started\u002Fnuxt","docs\u002Fauth-h3client\u002F00.getting-started\u002F00.nuxt",{"title":77,"path":274,"stem":275,"children":276},"\u002Fdocs\u002Fauth-h3client\u002Fessentials","docs\u002Fauth-h3client\u002F01.essentials\u002Findex",[277,278,282,286,290,294,298,301,304],{"title":77,"path":274,"stem":275},{"title":279,"path":280,"stem":281},"Session Management","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fsession","docs\u002Fauth-h3client\u002F01.essentials\u002F00.session",{"title":283,"path":284,"stem":285},"Route Protection","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Froute-protection","docs\u002Fauth-h3client\u002F01.essentials\u002F01.route-protection",{"title":287,"path":288,"stem":289},"CSRF Protection","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fcsrf","docs\u002Fauth-h3client\u002F01.essentials\u002F02.csrf",{"title":291,"path":292,"stem":293},"Auth Flows","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fauth-flows","docs\u002Fauth-h3client\u002F01.essentials\u002F03.auth-flows",{"title":295,"path":296,"stem":297},"OAuth and OIDC","\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Foauth","docs\u002Fauth-h3client\u002F01.essentials\u002F04.oauth",{"title":33,"path":299,"stem":300},"\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fbot-detection","docs\u002Fauth-h3client\u002F01.essentials\u002F05.bot-detection",{"title":155,"path":302,"stem":303},"\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Fcookies","docs\u002Fauth-h3client\u002F01.essentials\u002F06.cookies",{"title":143,"path":305,"stem":306},"\u002Fdocs\u002Fauth-h3client\u002Fessentials\u002Flogging","docs\u002Fauth-h3client\u002F01.essentials\u002F07.logging",{"title":123,"path":308,"stem":309,"children":310},"\u002Fdocs\u002Fauth-h3client\u002Fmfa","docs\u002Fauth-h3client\u002F02.mfa\u002Findex",[311,312,316,319,323,327],{"title":123,"path":308,"stem":309},{"title":313,"path":314,"stem":315},"Built-in MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fbuilt-in-flow","docs\u002Fauth-h3client\u002F02.mfa\u002F01.built-in-flow",{"title":163,"path":317,"stem":318},"\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fpassword-reset","docs\u002Fauth-h3client\u002F02.mfa\u002F02.password-reset",{"title":320,"path":321,"stem":322},"Email Change","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Femail-change","docs\u002Fauth-h3client\u002F02.mfa\u002F03.email-change",{"title":324,"path":325,"stem":326},"Custom MFA Flow","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fcustom-flow","docs\u002Fauth-h3client\u002F02.mfa\u002F04.custom-flow",{"title":328,"path":329,"stem":330},"Client-Side MFA","\u002Fdocs\u002Fauth-h3client\u002Fmfa\u002Fclient-side","docs\u002Fauth-h3client\u002F02.mfa\u002F05.client-side",{"title":332,"path":333,"stem":334,"children":335},"Client-side","\u002Fdocs\u002Fauth-h3client\u002Fclient","docs\u002Fauth-h3client\u002F03.client\u002Findex",[336,337,341,345,349],{"title":332,"path":333,"stem":334},{"title":338,"path":339,"stem":340},"useAuthData","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fuse-auth-data","docs\u002Fauth-h3client\u002F03.client\u002F00.use-auth-data",{"title":342,"path":343,"stem":344},"useMagicLink","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fuse-magic-link","docs\u002Fauth-h3client\u002F03.client\u002F01.use-magic-link",{"title":346,"path":347,"stem":348},"executeRequest","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fexecute-request","docs\u002Fauth-h3client\u002F03.client\u002F02.execute-request",{"title":350,"path":351,"stem":352},"getCsrfToken","\u002Fdocs\u002Fauth-h3client\u002Fclient\u002Fget-csrf-token","docs\u002Fauth-h3client\u002F03.client\u002F03.get-csrf-token",{"title":38,"path":354,"stem":355},"\u002Fdocs\u002Fauth-h3client\u002Fsecurity","docs\u002Fauth-h3client\u002F04.security",{"title":220,"path":357,"stem":358,"children":359,"page":53},"\u002Fdocs\u002Fauth-h3client\u002Fguides","docs\u002Fauth-h3client\u002F05.guides",[360,364,368,372],{"title":361,"path":362,"stem":363},"H3 and Nitro Setup","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fh3-nitro","docs\u002Fauth-h3client\u002F05.guides\u002F00.h3-nitro",{"title":365,"path":366,"stem":367},"HMAC Inter-service Auth","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fhmac","docs\u002Fauth-h3client\u002F05.guides\u002Fhmac",{"title":369,"path":370,"stem":371},"Image Upload","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fimage-upload","docs\u002Fauth-h3client\u002F05.guides\u002Fimage-upload",{"title":373,"path":374,"stem":375},"mTLS Configuration","\u002Fdocs\u002Fauth-h3client\u002Fguides\u002Fmtls","docs\u002Fauth-h3client\u002F05.guides\u002Fmtls",{"title":237,"path":377,"stem":378},"\u002Fdocs\u002Fauth-h3client\u002Fconfiguration","docs\u002Fauth-h3client\u002F06.configuration",{"title":246,"path":380,"stem":381,"children":382},"\u002Fdocs\u002Fauth-h3client\u002Fapi","docs\u002Fauth-h3client\u002F07.api\u002Findex",[383,384,387,390,394],{"title":246,"path":380,"stem":381},{"title":254,"path":385,"stem":386},"\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fcontrollers","docs\u002Fauth-h3client\u002F07.api\u002F00.controllers",{"title":250,"path":388,"stem":389},"\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fmiddleware","docs\u002Fauth-h3client\u002F07.api\u002F01.middleware",{"title":391,"path":392,"stem":393},"Client-side Reference","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Fcomposables","docs\u002Fauth-h3client\u002F07.api\u002F02.composables",{"title":395,"path":396,"stem":397},"Utilities","\u002Fdocs\u002Fauth-h3client\u002Fapi\u002Futilities","docs\u002Fauth-h3client\u002F07.api\u002F03.utilities",{"title":399,"path":35,"stem":400,"children":401},"Bot Detector","docs\u002Fbot-detection\u002Findex",[402,403,406,410,414,433,507,510,513],{"title":399,"path":35,"stem":400},{"title":14,"path":404,"stem":405},"\u002Fdocs\u002Fbot-detection\u002Fgetting-started","docs\u002Fbot-detection\u002F00.getting-started",{"title":407,"path":408,"stem":409},"CLI","\u002Fdocs\u002Fbot-detection\u002Fcli","docs\u002Fbot-detection\u002F01.cli",{"title":411,"path":412,"stem":413},"Data Sources","\u002Fdocs\u002Fbot-detection\u002Fdata-sources","docs\u002Fbot-detection\u002F02.data-sources",{"title":220,"path":415,"stem":416,"children":417,"page":53},"\u002Fdocs\u002Fbot-detection\u002Fguides","docs\u002Fbot-detection\u002F03.guides",[418,422,426,429],{"title":419,"path":420,"stem":421},"Custom Checkers","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fcustom","docs\u002Fbot-detection\u002F03.guides\u002FCUSTOM",{"title":423,"path":424,"stem":425},"Scheduling Database Generation","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fgenerate","docs\u002Fbot-detection\u002F03.guides\u002FGENERATE",{"title":143,"path":427,"stem":428},"\u002Fdocs\u002Fbot-detection\u002Fguides\u002Flogging","docs\u002Fbot-detection\u002F03.guides\u002FLOGGING",{"title":430,"path":431,"stem":432},"Score Modes and Reputation Healing","\u002Fdocs\u002Fbot-detection\u002Fguides\u002Fscore","docs\u002Fbot-detection\u002F03.guides\u002FSCORE",{"title":434,"path":435,"stem":436,"children":437},"Checkers","\u002Fdocs\u002Fbot-detection\u002Fcheckers","docs\u002Fbot-detection\u002F04.checkers\u002Findex",[438,439,443,447,451,455,459,463,467,471,475,479,483,487,491,495,499,503],{"title":434,"path":435,"stem":436},{"title":440,"path":441,"stem":442},"IP Validation","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fip-validation","docs\u002Fbot-detection\u002F04.checkers\u002F01.ip-validation",{"title":444,"path":445,"stem":446},"Good \u002F Bad Bot Verification","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fgood-bots","docs\u002Fbot-detection\u002F04.checkers\u002F02.good-bots",{"title":448,"path":449,"stem":450},"Browser & Device Fingerprint","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fbrowser-device","docs\u002Fbot-detection\u002F04.checkers\u002F03.browser-device",{"title":452,"path":453,"stem":454},"Locale Map","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Flocale-map","docs\u002Fbot-detection\u002F04.checkers\u002F04.locale-map",{"title":456,"path":457,"stem":458},"Known Threats","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-threats","docs\u002Fbot-detection\u002F04.checkers\u002F05.known-threats",{"title":460,"path":461,"stem":462},"ASN Classification","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fasn-classification","docs\u002Fbot-detection\u002F04.checkers\u002F06.asn-classification",{"title":464,"path":465,"stem":466},"Tor Analysis","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Ftor-analysis","docs\u002Fbot-detection\u002F04.checkers\u002F07.tor-analysis",{"title":468,"path":469,"stem":470},"Timezone Consistency","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Ftimezone-consistency","docs\u002Fbot-detection\u002F04.checkers\u002F08.timezone-consistency",{"title":472,"path":473,"stem":474},"Honeypot","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fhoneypot","docs\u002Fbot-detection\u002F04.checkers\u002F09.honeypot",{"title":476,"path":477,"stem":478},"Known Bad IPs","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-bad-ips","docs\u002Fbot-detection\u002F04.checkers\u002F10.known-bad-ips",{"title":480,"path":481,"stem":482},"Behavior Rate","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fbehavior-rate","docs\u002Fbot-detection\u002F04.checkers\u002F11.behavior-rate",{"title":484,"path":485,"stem":486},"Proxy \u002F ISP \u002F Cookie","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fproxy-isp-cookies","docs\u002Fbot-detection\u002F04.checkers\u002F12.proxy-isp-cookies",{"title":488,"path":489,"stem":490},"Session Coherence","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fsession-coherence","docs\u002Fbot-detection\u002F04.checkers\u002F13.session-coherence",{"title":492,"path":493,"stem":494},"Velocity Fingerprint","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fvelocity-fingerprint","docs\u002Fbot-detection\u002F04.checkers\u002F14.velocity-fingerprint",{"title":496,"path":497,"stem":498},"UA & Header Analysis","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fua-header","docs\u002Fbot-detection\u002F04.checkers\u002F15.ua-header",{"title":500,"path":501,"stem":502},"Geolocation","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fgeolocation","docs\u002Fbot-detection\u002F04.checkers\u002F16.geolocation",{"title":504,"path":505,"stem":506},"Known Bad User-Agents","\u002Fdocs\u002Fbot-detection\u002Fcheckers\u002Fknown-bad-ua","docs\u002Fbot-detection\u002F04.checkers\u002F17.known-bad-ua",{"title":38,"path":508,"stem":509},"\u002Fdocs\u002Fbot-detection\u002Fsecurity","docs\u002Fbot-detection\u002F04.security",{"title":246,"path":511,"stem":512},"\u002Fdocs\u002Fbot-detection\u002Fapi","docs\u002Fbot-detection\u002F05.api",{"title":237,"path":514,"stem":515},"\u002Fdocs\u002Fbot-detection\u002Fconfiguration","docs\u002Fbot-detection\u002F06.configuration",{"title":517,"path":11,"stem":518,"children":519},"Introduction","docs\u002Fgetting-started\u002Findex",[520],{"title":517,"path":11,"stem":518},{"title":27,"path":29,"stem":70,"children":522},[523,524,525,565,566,571,572],{"title":27,"path":29,"stem":70},{"title":14,"path":74,"stem":75},{"title":77,"path":78,"stem":79,"children":526},[527,528,529,530,531,532,533,534,535,536,537,538,539,540,541,542,543,544,545,546,547,548,549],{"title":77,"path":78,"stem":79},{"title":83,"path":84,"stem":85},{"title":87,"path":88,"stem":89},{"title":91,"path":92,"stem":93},{"title":95,"path":96,"stem":97},{"title":99,"path":100,"stem":101},{"title":103,"path":104,"stem":105},{"title":107,"path":108,"stem":109},{"title":111,"path":112,"stem":113},{"title":115,"path":116,"stem":117},{"title":119,"path":120,"stem":121},{"title":123,"path":124,"stem":125},{"title":127,"path":128,"stem":129},{"title":131,"path":132,"stem":133},{"title":135,"path":136,"stem":137},{"title":139,"path":140,"stem":141},{"title":143,"path":144,"stem":145},{"title":147,"path":148,"stem":149},{"title":151,"path":152,"stem":153},{"title":155,"path":156,"stem":157},{"title":159,"path":160,"stem":161},{"title":163,"path":164,"stem":165},{"title":167,"path":168,"stem":169,"children":550},[551,552,553,554,563,564],{"title":167,"path":168,"stem":169},{"title":173,"path":174,"stem":175},{"title":177,"path":178,"stem":179},{"title":181,"path":182,"stem":183,"children":555},[556,557,558,559,560,561,562],{"title":181,"path":182,"stem":183},{"title":187,"path":188,"stem":189},{"title":191,"path":192,"stem":193},{"title":195,"path":196,"stem":197},{"title":199,"path":200,"stem":201},{"title":203,"path":204,"stem":205},{"title":207,"path":208,"stem":209},{"title":147,"path":211,"stem":212},{"title":38,"path":214,"stem":215},{"title":38,"path":217,"stem":218},{"title":220,"path":221,"stem":222,"children":567,"page":53},[568,569,570],{"title":225,"path":226,"stem":227},{"title":229,"path":230,"stem":231},{"title":233,"path":234,"stem":235},{"title":237,"path":238,"stem":239},{"title":241,"path":242,"stem":243,"children":573,"page":53},[574,575,576],{"title":246,"path":247,"stem":248},{"title":250,"path":251,"stem":252},{"title":254,"path":255,"stem":256},{"title":40,"path":42,"stem":578,"children":579},"docs\u002Fshield-base\u002Findex",[580,581,584,588,629,633,637,641],{"title":40,"path":42,"stem":578},{"title":14,"path":582,"stem":583},"\u002Fdocs\u002Fshield-base\u002Fgetting-started","docs\u002Fshield-base\u002F00.getting-started",{"title":585,"path":586,"stem":587},"CLI Reference","\u002Fdocs\u002Fshield-base\u002Fcli","docs\u002Fshield-base\u002F01.cli",{"title":411,"path":589,"stem":590,"children":591},"\u002Fdocs\u002Fshield-base\u002Fdata-sources","docs\u002Fshield-base\u002F02.data-sources\u002Findex",[592,593,597,601,605,609,613,617,621,625],{"title":411,"path":589,"stem":590},{"title":594,"path":595,"stem":596},"BGP \u002F ASN","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fbgp","docs\u002Fshield-base\u002F02.data-sources\u002Fbgp",{"title":598,"path":599,"stem":600},"City Geolocation","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcity","docs\u002Fshield-base\u002F02.data-sources\u002Fcity",{"title":602,"path":603,"stem":604},"Country Geolocation","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcountry","docs\u002Fshield-base\u002F02.data-sources\u002Fcountry",{"title":606,"path":607,"stem":608},"Verified Crawlers","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fcrawlers","docs\u002Fshield-base\u002F02.data-sources\u002Fcrawlers",{"title":610,"path":611,"stem":612},"Disposable Emails","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Femail","docs\u002Fshield-base\u002F02.data-sources\u002Femail",{"title":614,"path":615,"stem":616},"FireHOL Threat Intelligence","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Ffirehol","docs\u002Fshield-base\u002F02.data-sources\u002Ffirehol",{"title":618,"path":619,"stem":620},"Proxy Detection","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fproxy","docs\u002Fshield-base\u002F02.data-sources\u002Fproxy",{"title":622,"path":623,"stem":624},"Tor Nodes","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Ftor","docs\u002Fshield-base\u002F02.data-sources\u002Ftor",{"title":626,"path":627,"stem":628},"Suspicious User-Agents","\u002Fdocs\u002Fshield-base\u002Fdata-sources\u002Fuseragent","docs\u002Fshield-base\u002F02.data-sources\u002Fuseragent",{"title":630,"path":631,"stem":632},"Programmatic Usage","\u002Fdocs\u002Fshield-base\u002Fusage","docs\u002Fshield-base\u002F03.usage",{"title":634,"path":635,"stem":636},"Custom Data Sources","\u002Fdocs\u002Fshield-base\u002Fcustom-data-sources","docs\u002Fshield-base\u002F04.custom-data-sources",{"title":638,"path":639,"stem":640},"TypeScript Types","\u002Fdocs\u002Fshield-base\u002Ftypes","docs\u002Fshield-base\u002F05.types",{"title":246,"path":642,"stem":643},"\u002Fdocs\u002Fshield-base\u002Fapi","docs\u002Fshield-base\u002F06.api",{"title":395,"path":48,"stem":645,"children":646},"docs\u002Futils\u002Findex",[647,648,665,698,795],{"title":395,"path":48,"stem":645},{"title":649,"path":650,"stem":651,"children":652,"page":53},"Eslint","\u002Fdocs\u002Futils\u002Feslint","docs\u002Futils\u002Feslint",[653,657,661],{"title":654,"path":655,"stem":656},"React Config","\u002Fdocs\u002Futils\u002Feslint\u002Freact","docs\u002Futils\u002Feslint\u002Freact",{"title":658,"path":659,"stem":660},"TypeScript Config","\u002Fdocs\u002Futils\u002Feslint\u002Ftypescript","docs\u002Futils\u002Feslint\u002Ftypescript",{"title":662,"path":663,"stem":664},"Vue Config","\u002Fdocs\u002Futils\u002Feslint\u002Fvue","docs\u002Futils\u002Feslint\u002Fvue",{"title":666,"path":667,"stem":668,"children":669,"page":53},"Server","\u002Fdocs\u002Futils\u002Fserver","docs\u002Futils\u002Fserver",[670,674,678,682,686,690,694],{"title":671,"path":672,"stem":673},"Encryption","\u002Fdocs\u002Futils\u002Fserver\u002Fencryption","docs\u002Futils\u002Fserver\u002Fencryption",{"title":675,"path":676,"stem":677},"Path Resolver","\u002Fdocs\u002Futils\u002Fserver\u002Fpathresolver","docs\u002Futils\u002Fserver\u002FpathResolver",{"title":679,"path":680,"stem":681},"File Replacements","\u002Fdocs\u002Futils\u002Fserver\u002Freplace","docs\u002Futils\u002Fserver\u002Freplace",{"title":683,"path":684,"stem":685},"run","\u002Fdocs\u002Futils\u002Fserver\u002Frun","docs\u002Futils\u002Fserver\u002Frun",{"title":687,"path":688,"stem":689},"scheduleTask","\u002Fdocs\u002Futils\u002Fserver\u002Fscheduletask","docs\u002Futils\u002Fserver\u002FscheduleTask",{"title":691,"path":692,"stem":693},"spawnRun","\u002Fdocs\u002Futils\u002Fserver\u002Fspawnrun","docs\u002Futils\u002Fserver\u002FspawnRun",{"title":695,"path":696,"stem":697},"uploadCsv","\u002Fdocs\u002Futils\u002Fserver\u002Fuploadcsv","docs\u002Futils\u002Fserver\u002FuploadCsv",{"title":699,"path":700,"stem":701,"children":702,"page":53},"Shared","\u002Fdocs\u002Futils\u002Fshared","docs\u002Futils\u002Fshared",[703,707,711,715,719,723,727,731,735,739,743,747,751,755,759,763,767,771,775,779,783,787,791],{"title":704,"path":705,"stem":706},"BatchQueue","\u002Fdocs\u002Futils\u002Fshared\u002Fbatchqueue","docs\u002Futils\u002Fshared\u002FbatchQueue",{"title":708,"path":709,"stem":710},"capitalize","\u002Fdocs\u002Futils\u002Fshared\u002Fcapitalize","docs\u002Futils\u002Fshared\u002Fcapitalize",{"title":712,"path":713,"stem":714},"chunkProcess","\u002Fdocs\u002Futils\u002Fshared\u002Fchunkprocess","docs\u002Futils\u002Fshared\u002FchunkProcess",{"title":716,"path":717,"stem":718},"cleanObject","\u002Fdocs\u002Futils\u002Fshared\u002Fcleanobject","docs\u002Futils\u002Fshared\u002FcleanObject",{"title":720,"path":721,"stem":722},"createConfigManager","\u002Fdocs\u002Futils\u002Fshared\u002Fconfigurationdefiner","docs\u002Futils\u002Fshared\u002FconfigurationDefiner",{"title":724,"path":725,"stem":726},"debounce","\u002Fdocs\u002Futils\u002Fshared\u002Fdebounce","docs\u002Futils\u002Fshared\u002Fdebounce",{"title":728,"path":729,"stem":730},"ensureArray","\u002Fdocs\u002Futils\u002Fshared\u002Fensurearray","docs\u002Futils\u002Fshared\u002FensureArray",{"title":732,"path":733,"stem":734},"fetchWithRetry","\u002Fdocs\u002Futils\u002Fshared\u002Ffetchwithretry","docs\u002Futils\u002Fshared\u002FfetchWithRetry",{"title":736,"path":737,"stem":738},"filterEmptyValues","\u002Fdocs\u002Futils\u002Fshared\u002Ffilteremptyvalues","docs\u002Futils\u002Fshared\u002FfilterEmptyValues",{"title":740,"path":741,"stem":742},"findStringsInObject","\u002Fdocs\u002Futils\u002Fshared\u002Ffindobjectvalues","docs\u002Futils\u002Fshared\u002FfindObjectValues",{"title":744,"path":745,"stem":746},"fisherYatesShuffle","\u002Fdocs\u002Futils\u002Fshared\u002Ffisheryatesshuffle","docs\u002Futils\u002Fshared\u002FfisherYatesShuffle",{"title":748,"path":749,"stem":750},"getRandomImage","\u002Fdocs\u002Futils\u002Fshared\u002Fgetrandomimage","docs\u002Futils\u002Fshared\u002FgetRandomImage",{"title":752,"path":753,"stem":754},"isObjectHasValues","\u002Fdocs\u002Futils\u002Fshared\u002Fisobjecthasvalues","docs\u002Futils\u002Fshared\u002FisObjectHasValues",{"title":756,"path":757,"stem":758},"isAsyncOrPromise","\u002Fdocs\u002Futils\u002Fshared\u002Fispromise","docs\u002Futils\u002Fshared\u002FisPromise",{"title":760,"path":761,"stem":762},"MiniCache","\u002Fdocs\u002Futils\u002Fshared\u002Fminicache","docs\u002Futils\u002Fshared\u002FminiCache",{"title":764,"path":765,"stem":766},"parseCookies","\u002Fdocs\u002Futils\u002Fshared\u002Fparserawcookies","docs\u002Futils\u002Fshared\u002FparseRawCookies",{"title":768,"path":769,"stem":770},"safeAction","\u002Fdocs\u002Futils\u002Fshared\u002Fpromiselocker","docs\u002Futils\u002Fshared\u002FpromiseLocker",{"title":772,"path":773,"stem":774},"Random","\u002Fdocs\u002Futils\u002Fshared\u002Frandom","docs\u002Futils\u002Fshared\u002Frandom",{"title":776,"path":777,"stem":778},"range","\u002Fdocs\u002Futils\u002Fshared\u002Frange","docs\u002Futils\u002Fshared\u002Frange",{"title":780,"path":781,"stem":782},"rateLimiters","\u002Fdocs\u002Futils\u002Fshared\u002Fratelimiters","docs\u002Futils\u002Fshared\u002FrateLimiters",{"title":784,"path":785,"stem":786},"safeObjectMerge","\u002Fdocs\u002Futils\u002Fshared\u002Fsafemerge","docs\u002Futils\u002Fshared\u002FsafeMerge",{"title":788,"path":789,"stem":790},"textTruncation","\u002Fdocs\u002Futils\u002Fshared\u002Ftexttruncation","docs\u002Futils\u002Fshared\u002FtextTruncation",{"title":792,"path":793,"stem":794},"validateZodSchema","\u002Fdocs\u002Futils\u002Fshared\u002Fvalidatezodschema","docs\u002Futils\u002Fshared\u002FvalidateZodSchema",{"title":796,"path":797,"stem":798,"children":799},"Utility Types","\u002Fdocs\u002Futils\u002Ftypes","docs\u002Futils\u002Ftypes\u002Findex",[800,801,805,809,813,817,821,825,829,833],{"title":796,"path":797,"stem":798},{"title":802,"path":803,"stem":804},"Brand","\u002Fdocs\u002Futils\u002Ftypes\u002Fbrand","docs\u002Futils\u002Ftypes\u002FBrand",{"title":806,"path":807,"stem":808},"DeepPartial","\u002Fdocs\u002Futils\u002Ftypes\u002Fdeeppartial","docs\u002Futils\u002Ftypes\u002FDeepPartial",{"title":810,"path":811,"stem":812},"Merge","\u002Fdocs\u002Futils\u002Ftypes\u002Fmerge","docs\u002Futils\u002Ftypes\u002FMerge",{"title":814,"path":815,"stem":816},"NonNullable","\u002Fdocs\u002Futils\u002Ftypes\u002Fnonnullable","docs\u002Futils\u002Ftypes\u002FNonNullable",{"title":818,"path":819,"stem":820},"Prettify","\u002Fdocs\u002Futils\u002Ftypes\u002Fprettify","docs\u002Futils\u002Ftypes\u002FPrettify",{"title":822,"path":823,"stem":824},"PromiseType","\u002Fdocs\u002Futils\u002Ftypes\u002Fpromisetype","docs\u002Futils\u002Ftypes\u002FPromiseType",{"title":826,"path":827,"stem":828},"RequireKeys","\u002Fdocs\u002Futils\u002Ftypes\u002Frequirekeys","docs\u002Futils\u002Ftypes\u002FRequireKeys",{"title":830,"path":831,"stem":832},"StandardResponse","\u002Fdocs\u002Futils\u002Ftypes\u002Fstandardresponse","docs\u002Futils\u002Ftypes\u002FStandardResponse",{"title":834,"path":835,"stem":836},"ValueOf","\u002Fdocs\u002Futils\u002Ftypes\u002Fvalueof","docs\u002Futils\u002Ftypes\u002FValueOf",{"id":4,"extension":5,"links":838,"meta":849,"stem":62,"__hash__":63},[839,847,848],{"nested":8,"label":9,"icon":10,"to":11,"children":840},[841,842,843,844,845,846],{"label":14,"icon":15,"to":11,"description":16,"github":17,"badge":18},{"label":20,"icon":21,"to":22,"description":23,"github":24,"badge":25},{"label":27,"icon":28,"to":29,"description":30,"github":31,"badge":25},{"label":33,"icon":34,"to":35,"description":36,"github":37,"badge":38},{"label":40,"icon":41,"to":42,"description":43,"github":44,"badge":38},{"label":46,"icon":47,"to":48,"description":49,"github":50,"badge":51},{"nested":53,"label":54,"icon":55,"to":56},{"nested":53,"label":58,"icon":59,"to":60},{},{"id":851,"title":123,"body":852,"description":2783,"extension":2784,"icon":2785,"meta":2786,"module":2787,"navigation":8,"path":124,"rawbody":2788,"seo":2789,"stem":125,"__hash__":2790},"docs\u002Fdocs\u002Fiam\u002F01.essentials\u002F10.mfa.md",{"type":853,"value":854,"toc":2755},"minimark",[855,873,876,879,884,900,994,1000,1297,1300,1350,1372,1391,1393,1396,1405,1500,1503,1544,1551,1569,1654,1664,1666,1670,1675,1679,1682,1743,1812,1816,1973,1978,1980,1984,1987,1991,1994,2047,2051,2065,2075,2111,2118,2166,2169,2173,2176,2229,2235,2239,2242,2288,2300,2302,2306,2309,2474,2479,2481,2485,2498,2506,2508,2512,2630,2632,2636,2640,2669,2673,2682,2686,2735,2739,2751],[856,857,858,859,863,864,868,869,872],"p",{},"The IAM service provides two MFA mechanisms: ",[860,861,862],"strong",{},"adaptive MFA"," triggered automatically by ",[865,866,867],"a",{"href":96},"anomaly detection"," when a suspicious session event occurs, and ",[860,870,871],{},"custom MFA flows"," that your application triggers explicitly for sensitive user actions such as changing a password, transferring funds, or deleting an account.",[856,874,875],{},"Both mechanisms share the same infrastructure: a signed temporary JWT link, a 7-digit OTP code stored as a SHA-256 hash with a 7-minute expiry, multi-layer rate limiting, and an atomic verification function that rotates tokens on success.",[877,878],"hr",{},[880,881,883],"h2",{"id":882},"adaptive-mfa-lifecycle","Adaptive MFA lifecycle",[856,885,886,887,891,892,895,896,899],{},"When ",[888,889,890],"code",{},"strangeThings()"," determines that a session event is suspicious (IP change, device mismatch, session limit exceeded, or proxy\u002Fhosting anomaly), it returns ",[888,893,894],{},"reqMFA: true",". The calling controller then invokes ",[888,897,898],{},"sendTempMfaLink",", which triggers the full adaptive MFA flow.",[901,902,904,909,934,938,953,957,972,976,979,983],"steps",{"level":903},"4",[905,906,908],"h4",{"id":907},"link-generation","Link generation",[856,910,911,913,914,917,918,921,922,925,926,929,930,933],{},[888,912,898],{}," generates a unique JTI (",[888,915,916],{},"crypto.randomUUID()"," + ",[888,919,920],{},"crypto.randomBytes(64).toString('hex')",", producing a 164-character string), a random challenge (",[888,923,924],{},"crypto.randomBytes(128)","), and hashes the challenge with SHA-256. It then signs a temporary JWT with purpose ",[888,927,928],{},"MAGIC_LINK_MFA_CHECKS"," and a subject of ",[888,931,932],{},"MAGIC_LINK_MFA_CHECKS_{visitor}",".",[905,935,937],{"id":936},"otp-generation","OTP generation",[856,939,940,941,944,945,948,949,952],{},"The function calls ",[888,942,943],{},"generateMfaCode"," (described in ",[865,946,937],{"href":947},"#otp-generation",") to create a 7-digit code, hash it, and store it in the ",[888,950,951],{},"mfa_codes"," table tied to the user's current refresh token.",[905,954,956],{"id":955},"email-delivery","Email delivery",[856,958,959,960,963,964,968,969,933],{},"The function fetches the user's email and display name from the database, then calls ",[888,961,962],{},"mfaEmail"," to render the ",[865,965,967],{"href":966},"\u002Fdocs\u002Fiam\u002Fessentials\u002Femails#otp-template","OTP email template"," with the code, magic link URL, and contextual metadata (device, browser, location). The email subject is formatted as ",[888,970,971],{},"Security Code - {code}",[905,973,975],{"id":974},"user-interaction","User interaction",[856,977,978],{},"The user receives an email with the 7-digit code displayed prominently and a \"Verify Here\" button linking to the magic link URL. They can either click the link (which pre-fills the code through the bounce page) or copy the code manually into the verification form.",[905,980,982],{"id":981},"verification","Verification",[856,984,985,986,989,990,993],{},"The user submits the code to the MFA verification route. ",[888,987,988],{},"verifyMfaCode"," validates the code, rotates the user's tokens, updates their fingerprints, and records ",[888,991,992],{},"last_mfa_at"," on the user record. The flow completes with new access and refresh tokens.",[995,996,998],"h3",{"id":997},"sendtempmfalink",[888,999,898],{},[1001,1002,1007],"pre",{"className":1003,"code":1004,"language":1005,"meta":1006,"style":1006},"language-ts shiki shiki-themes light-plus light-plus dracula","import { sendTempMfaLink } from '@riavzon\u002Fauth'\n\nconst result = await sendTempMfaLink(\n  { userId: user.id, visitor: user.visitor_id },\n  rawRefreshToken,\n  req.ip!,\n  res,\n  {\n    device: 'Safari on iOS',\n    browser: 'Safari 17',\n    location: 'Paris, FR',\n  }\n)\n\nif (result === 'rate_limited') {\n  res.status(429).json({ error: 'Too many MFA requests. Try again later.' })\n  return\n}\n","ts","",[888,1008,1009,1042,1048,1073,1112,1121,1137,1145,1151,1169,1186,1203,1209,1215,1220,1245,1285,1291],{"__ignoreMap":1006},[1010,1011,1014,1018,1022,1025,1028,1031,1035,1039],"span",{"class":1012,"line":1013},"line",1,[1010,1015,1017],{"class":1016},"sZ328","import",[1010,1019,1021],{"class":1020},"sDd4n"," { ",[1010,1023,898],{"class":1024},"sjsA6",[1010,1026,1027],{"class":1020}," } ",[1010,1029,1030],{"class":1016},"from",[1010,1032,1034],{"class":1033},"sFkSl"," '",[1010,1036,1038],{"class":1037},"sFB1V","@riavzon\u002Fauth",[1010,1040,1041],{"class":1033},"'\n",[1010,1043,1045],{"class":1012,"line":1044},2,[1010,1046,1047],{"emptyLinePlaceholder":8},"\n",[1010,1049,1051,1055,1059,1063,1066,1070],{"class":1012,"line":1050},3,[1010,1052,1054],{"class":1053},"sl46w","const",[1010,1056,1058],{"class":1057},"s3JHE"," result",[1010,1060,1062],{"class":1061},"saOXh"," =",[1010,1064,1065],{"class":1016}," await",[1010,1067,1069],{"class":1068},"sHOzp"," sendTempMfaLink",[1010,1071,1072],{"class":1020},"(\n",[1010,1074,1076,1079,1082,1086,1089,1091,1094,1097,1100,1102,1104,1106,1109],{"class":1012,"line":1075},4,[1010,1077,1078],{"class":1020},"  { ",[1010,1080,1081],{"class":1024},"userId",[1010,1083,1085],{"class":1084},"s34zl",":",[1010,1087,1088],{"class":1024}," user",[1010,1090,933],{"class":1020},[1010,1092,1093],{"class":1024},"id",[1010,1095,1096],{"class":1020},", ",[1010,1098,1099],{"class":1024},"visitor",[1010,1101,1085],{"class":1084},[1010,1103,1088],{"class":1024},[1010,1105,933],{"class":1020},[1010,1107,1108],{"class":1024},"visitor_id",[1010,1110,1111],{"class":1020}," },\n",[1010,1113,1115,1118],{"class":1012,"line":1114},5,[1010,1116,1117],{"class":1024},"  rawRefreshToken",[1010,1119,1120],{"class":1020},",\n",[1010,1122,1124,1127,1129,1132,1135],{"class":1012,"line":1123},6,[1010,1125,1126],{"class":1024},"  req",[1010,1128,933],{"class":1020},[1010,1130,1131],{"class":1024},"ip",[1010,1133,1134],{"class":1061},"!",[1010,1136,1120],{"class":1020},[1010,1138,1140,1143],{"class":1012,"line":1139},7,[1010,1141,1142],{"class":1024},"  res",[1010,1144,1120],{"class":1020},[1010,1146,1148],{"class":1012,"line":1147},8,[1010,1149,1150],{"class":1020},"  {\n",[1010,1152,1154,1157,1159,1161,1164,1167],{"class":1012,"line":1153},9,[1010,1155,1156],{"class":1024},"    device",[1010,1158,1085],{"class":1084},[1010,1160,1034],{"class":1033},[1010,1162,1163],{"class":1037},"Safari on iOS",[1010,1165,1166],{"class":1033},"'",[1010,1168,1120],{"class":1020},[1010,1170,1172,1175,1177,1179,1182,1184],{"class":1012,"line":1171},10,[1010,1173,1174],{"class":1024},"    browser",[1010,1176,1085],{"class":1084},[1010,1178,1034],{"class":1033},[1010,1180,1181],{"class":1037},"Safari 17",[1010,1183,1166],{"class":1033},[1010,1185,1120],{"class":1020},[1010,1187,1189,1192,1194,1196,1199,1201],{"class":1012,"line":1188},11,[1010,1190,1191],{"class":1024},"    location",[1010,1193,1085],{"class":1084},[1010,1195,1034],{"class":1033},[1010,1197,1198],{"class":1037},"Paris, FR",[1010,1200,1166],{"class":1033},[1010,1202,1120],{"class":1020},[1010,1204,1206],{"class":1012,"line":1205},12,[1010,1207,1208],{"class":1020},"  }\n",[1010,1210,1212],{"class":1012,"line":1211},13,[1010,1213,1214],{"class":1020},")\n",[1010,1216,1218],{"class":1012,"line":1217},14,[1010,1219,1047],{"emptyLinePlaceholder":8},[1010,1221,1223,1226,1229,1232,1235,1237,1240,1242],{"class":1012,"line":1222},15,[1010,1224,1225],{"class":1016},"if",[1010,1227,1228],{"class":1020}," (",[1010,1230,1231],{"class":1024},"result",[1010,1233,1234],{"class":1061}," ===",[1010,1236,1034],{"class":1033},[1010,1238,1239],{"class":1037},"rate_limited",[1010,1241,1166],{"class":1033},[1010,1243,1244],{"class":1020},") {\n",[1010,1246,1248,1250,1252,1255,1258,1262,1265,1267,1270,1273,1275,1277,1280,1282],{"class":1012,"line":1247},16,[1010,1249,1142],{"class":1024},[1010,1251,933],{"class":1020},[1010,1253,1254],{"class":1068},"status",[1010,1256,1257],{"class":1020},"(",[1010,1259,1261],{"class":1260},"spgvN","429",[1010,1263,1264],{"class":1020},").",[1010,1266,5],{"class":1068},[1010,1268,1269],{"class":1020},"({ ",[1010,1271,1272],{"class":1024},"error",[1010,1274,1085],{"class":1084},[1010,1276,1034],{"class":1033},[1010,1278,1279],{"class":1037},"Too many MFA requests. Try again later.",[1010,1281,1166],{"class":1033},[1010,1283,1284],{"class":1020}," })\n",[1010,1286,1288],{"class":1012,"line":1287},17,[1010,1289,1290],{"class":1016},"  return\n",[1010,1292,1294],{"class":1012,"line":1293},18,[1010,1295,1296],{"class":1020},"}\n",[856,1298,1299],{},"The function returns one of three values:",[1301,1302,1303,1316],"table",{},[1304,1305,1306],"thead",{},[1307,1308,1309,1313],"tr",{},[1310,1311,1312],"th",{},"Return value",[1310,1314,1315],{},"Meaning",[1317,1318,1319,1330,1340],"tbody",{},[1307,1320,1321,1327],{},[1322,1323,1324],"td",{},[888,1325,1326],{},"true",[1322,1328,1329],{},"Email sent successfully",[1307,1331,1332,1337],{},[1322,1333,1334],{},[888,1335,1336],{},"false",[1322,1338,1339],{},"An error occurred during code generation or email delivery",[1307,1341,1342,1347],{},[1322,1343,1344],{},[888,1345,1346],{},"'rate_limited'",[1322,1348,1349],{},"One of the three rate limiters rejected the request",[856,1351,1352,1353,1356,1357,1096,1360,1363,1364,1367,1368,1371],{},"The metadata parameter accepts the ",[888,1354,1355],{},"EmailMetaDataOTP"," shape with three fields: ",[888,1358,1359],{},"device",[888,1361,1362],{},"browser",", and ",[888,1365,1366],{},"location",". The ",[888,1369,1370],{},"date"," field shown in the email template is generated server-side automatically.",[1373,1374,1375],"warning",{},[856,1376,1377,1379,1380,1383,1384,1387,1388,1390],{},[888,1378,898],{}," applies three independent rate limiters before sending: a global email limiter (keyed ",[888,1381,1382],{},"'global_emails'","), a per-user limiter (keyed ",[888,1385,1386],{},"'user_{userId}'","), and a per-IP limiter. If any one of them rejects the request, the function returns ",[888,1389,1346],{}," without sending.",[877,1392],{},[880,1394,937],{"id":1395},"otp-generation-1",[856,1397,1398,1400,1401,1404],{},[888,1399,943],{}," produces a cryptographically secure 7-digit numeric code using ",[888,1402,1403],{},"crypto.randomInt(1_000_000, 9_999_999)",", zero-padded to 7 characters. The raw code is returned to the caller for email delivery, while a SHA-256 hash of the code is stored in the database.",[1001,1406,1408],{"className":1003,"code":1407,"language":1005,"meta":1006,"style":1006},"import { generateMfaCode } from '@riavzon\u002Fauth'\n\nconst result = await generateMfaCode(log, sessionToken, userId, jti)\n\nif (result.ok) {\n  \u002F\u002F result.code  — the raw 7-digit string, send to the user\n  \u002F\u002F result.data  — insertion metadata\n}\n",[888,1409,1410,1428,1432,1466,1470,1485,1491,1496],{"__ignoreMap":1006},[1010,1411,1412,1414,1416,1418,1420,1422,1424,1426],{"class":1012,"line":1013},[1010,1413,1017],{"class":1016},[1010,1415,1021],{"class":1020},[1010,1417,943],{"class":1024},[1010,1419,1027],{"class":1020},[1010,1421,1030],{"class":1016},[1010,1423,1034],{"class":1033},[1010,1425,1038],{"class":1037},[1010,1427,1041],{"class":1033},[1010,1429,1430],{"class":1012,"line":1044},[1010,1431,1047],{"emptyLinePlaceholder":8},[1010,1433,1434,1436,1438,1440,1442,1445,1447,1450,1452,1455,1457,1459,1461,1464],{"class":1012,"line":1050},[1010,1435,1054],{"class":1053},[1010,1437,1058],{"class":1057},[1010,1439,1062],{"class":1061},[1010,1441,1065],{"class":1016},[1010,1443,1444],{"class":1068}," generateMfaCode",[1010,1446,1257],{"class":1020},[1010,1448,1449],{"class":1024},"log",[1010,1451,1096],{"class":1020},[1010,1453,1454],{"class":1024},"sessionToken",[1010,1456,1096],{"class":1020},[1010,1458,1081],{"class":1024},[1010,1460,1096],{"class":1020},[1010,1462,1463],{"class":1024},"jti",[1010,1465,1214],{"class":1020},[1010,1467,1468],{"class":1012,"line":1075},[1010,1469,1047],{"emptyLinePlaceholder":8},[1010,1471,1472,1474,1476,1478,1480,1483],{"class":1012,"line":1114},[1010,1473,1225],{"class":1016},[1010,1475,1228],{"class":1020},[1010,1477,1231],{"class":1024},[1010,1479,933],{"class":1020},[1010,1481,1482],{"class":1024},"ok",[1010,1484,1244],{"class":1020},[1010,1486,1487],{"class":1012,"line":1123},[1010,1488,1490],{"class":1489},"sghk6","  \u002F\u002F result.code  — the raw 7-digit string, send to the user\n",[1010,1492,1493],{"class":1012,"line":1139},[1010,1494,1495],{"class":1489},"  \u002F\u002F result.data  — insertion metadata\n",[1010,1497,1498],{"class":1012,"line":1147},[1010,1499,1296],{"class":1020},[856,1501,1502],{},"The function uses a database transaction with idempotency logic:",[901,1504,1505,1509,1523,1527,1530,1534],{"level":903},[905,1506,1508],{"id":1507},"check-for-existing-code","Check for existing code",[856,1510,1511,1512,1514,1515,1518,1519,1522],{},"Before generating a new code, the function queries the ",[888,1513,951],{}," table for an unexpired, unused code belonging to the same user. If one exists, it skips insertion and returns ",[888,1516,1517],{},"ok: true"," with ",[888,1520,1521],{},"code: undefined"," to signal that a valid code is already in flight.",[905,1524,1526],{"id":1525},"clean-up-old-codes","Clean up old codes",[856,1528,1529],{},"Any expired or previously used codes for the user are deleted from the table.",[905,1531,1533],{"id":1532},"insert-new-code","Insert new code",[856,1535,1536,1537,1540,1541,933],{},"A new row is inserted with the SHA-256 hashed code, the hashed session token, the JTI, the user ID, and an expiry timestamp set to 7 minutes from now. The ",[888,1538,1539],{},"used"," column defaults to ",[888,1542,1543],{},"0",[995,1545,1547,1548,1550],{"id":1546},"the-mfa_codes-table","The ",[888,1549,951],{}," table",[856,1552,1553,1554,1556,1557,1096,1560,1096,1563,1363,1565,1568],{},"Each row in the ",[888,1555,951],{}," table represents a single pending OTP challenge. The table enforces uniqueness on ",[888,1558,1559],{},"user_id",[888,1561,1562],{},"code_hash",[888,1564,1463],{},[888,1566,1567],{},"token",", meaning each user can have at most one active code at any time.",[1301,1570,1571,1581],{},[1304,1572,1573],{},[1307,1574,1575,1578],{},[1310,1576,1577],{},"Column",[1310,1579,1580],{},"Purpose",[1317,1582,1583,1599,1614,1623,1632,1642],{},[1307,1584,1585,1589],{},[1322,1586,1587],{},[888,1588,1559],{},[1322,1590,1591,1592,1518,1595,1598],{},"The user the code belongs to. Foreign key to ",[888,1593,1594],{},"users.id",[888,1596,1597],{},"CASCADE"," delete.",[1307,1600,1601,1605],{},[1322,1602,1603],{},[888,1604,1567],{},[1322,1606,1607,1608,1518,1611,1613],{},"The hashed refresh token that was active when the code was issued. Foreign key to ",[888,1609,1610],{},"refresh_tokens.token",[888,1612,1597],{}," delete. If the token is revoked, the code is automatically invalidated.",[1307,1615,1616,1620],{},[1322,1617,1618],{},[888,1619,1463],{},[1322,1621,1622],{},"The JWT ID from the temporary link. Uniquely identifies the verification session.",[1307,1624,1625,1629],{},[1322,1626,1627],{},[888,1628,1562],{},[1322,1630,1631],{},"SHA-256 hash of the 7-digit code. Never stores the raw code.",[1307,1633,1634,1639],{},[1322,1635,1636],{},[888,1637,1638],{},"expires_at",[1322,1640,1641],{},"UTC timestamp. The code is invalid after this time (7 minutes from creation).",[1307,1643,1644,1648],{},[1322,1645,1646],{},[888,1647,1539],{},[1322,1649,1650,1651,1653],{},"Boolean flag. Set to ",[888,1652,1543],{}," on creation, never updated (codes are deleted on use, not marked used).",[1373,1655,1656],{},[856,1657,1547,1658,1660,1661,1663],{},[888,1659,1567],{}," foreign key with ",[888,1662,1597],{}," delete means revoking a user's refresh token (through logout, token rotation, or admin action) automatically invalidates any pending MFA code tied to that token.",[877,1665],{},[880,1667,1669],{"id":1668},"otp-verification","OTP verification",[856,1671,1672,1674],{},[888,1673,988],{}," is the central verification function used by all MFA flows: adaptive MFA, custom MFA, and email update. It validates the submitted code, performs the database lookup, and on success rotates tokens, updates fingerprints, and records the verification timestamp.",[995,1676,1678],{"id":1677},"rate-limiting","Rate limiting",[856,1680,1681],{},"Verification is protected by four rate limiters and three consecutive-failure caches that work together to prevent brute-force attacks:",[1301,1683,1684,1696],{},[1304,1685,1686],{},[1307,1687,1688,1691,1694],{},[1310,1689,1690],{},"Limiter",[1310,1692,1693],{},"Key",[1310,1695,1580],{},[1317,1697,1698,1713,1728],{},[1307,1699,1700,1705,1710],{},[1322,1701,1702],{},[888,1703,1704],{},"uniLimiter",[1322,1706,1707],{},[888,1708,1709],{},"{IP}_{JTI}",[1322,1711,1712],{},"Limits attempts per IP and verification session",[1307,1714,1715,1720,1725],{},[1322,1716,1717],{},[888,1718,1719],{},"ipLimit",[1322,1721,1722],{},[888,1723,1724],{},"{code_hash}",[1322,1726,1727],{},"Blocks a specific code hash after verification (prevents replay)",[1307,1729,1730,1735,1740],{},[1322,1731,1732],{},[888,1733,1734],{},"usedJtiLimiter",[1322,1736,1737],{},[888,1738,1739],{},"{JTI}",[1322,1741,1742],{},"Block-only limiter that prevents reuse of a verified JTI",[1301,1744,1745,1759],{},[1304,1746,1747],{},[1307,1748,1749,1752,1754,1757],{},[1310,1750,1751],{},"Consecutive cache",[1310,1753,1693],{},[1310,1755,1756],{},"Duration",[1310,1758,1580],{},[1317,1760,1761,1778,1795],{},[1307,1762,1763,1768,1772,1775],{},[1322,1764,1765],{},[888,1766,1767],{},"consecutiveForSubmittedHash",[1322,1769,1770],{},[888,1771,1724],{},[1322,1773,1774],{},"10 minutes",[1322,1776,1777],{},"Tracks repeated submissions of the same wrong code",[1307,1779,1780,1785,1790,1792],{},[1322,1781,1782],{},[888,1783,1784],{},"consecutiveForSlowDown",[1322,1786,1787],{},[888,1788,1789],{},"{IP}",[1322,1791,1774],{},[1322,1793,1794],{},"Progressive slowdown on repeated failures from same IP",[1307,1796,1797,1802,1806,1809],{},[1322,1798,1799],{},[888,1800,1801],{},"consecutiveForJti",[1322,1803,1804],{},[888,1805,1739],{},[1322,1807,1808],{},"20 minutes",[1322,1810,1811],{},"Tracks failures per verification session",[995,1813,1815],{"id":1814},"verification-flow","Verification flow",[901,1817,1818,1822,1825,1829,1846,1850,1861,1865,1874,1878,1885,1889,1908,1912,1918,1922,1932,1936,1943,1947,1966,1970],{":level":903},[905,1819,1821],{"id":1820},"input-validation","Input validation",[856,1823,1824],{},"The submitted code is validated with a Zod schema to ensure it matches the expected format before any database query runs.",[905,1826,1828],{"id":1827},"hash-and-lookup","Hash and lookup",[856,1830,1831,1832,1834,1835,1837,1838,1841,1842,1845],{},"The submitted code is SHA-256 hashed. The function queries the ",[888,1833,951],{}," table for a row matching the JTI, the code hash, an ",[888,1836,1638],{}," in the future, and ",[888,1839,1840],{},"used = 0",". The query uses ",[888,1843,1844],{},"FOR UPDATE"," to lock the row.",[905,1847,1849],{"id":1848},"atomic-delete","Atomic delete",[856,1851,1852,1853,1856,1857,1860],{},"If a matching row is found, it is deleted with a ",[888,1854,1855],{},"LIMIT 1"," constraint. The function checks that ",[888,1858,1859],{},"affectedRows === 1"," to confirm the delete was atomic. This prevents time-of-check\u002Ftime-of-use (TOCTOU) races where two concurrent requests could both verify the same code.",[905,1862,1864],{"id":1863},"post-verification-blocking","Post-verification blocking",[856,1866,1867,1868,1870,1871,1873],{},"The verified code hash is blocked in ",[888,1869,1719],{}," for 10 minutes, and the JTI is blocked in ",[888,1872,1734],{}," for 20 minutes. This prevents replay of the same code or reuse of the verification session.",[905,1875,1877],{"id":1876},"optional-callback","Optional callback",[856,1879,1880,1881,1884],{},"If the caller passed an ",[888,1882,1883],{},"onSuccess"," callback (used by the email update flow to change the email address), it executes inside the same database transaction. If the callback throws, the entire transaction rolls back and the code is not consumed.",[905,1886,1888],{"id":1887},"user-record-update","User record update",[856,1890,1891,1892,1895,1896,1899,1900,1903,1904,1907],{},"The function updates ",[888,1893,1894],{},"users.last_mfa_at"," to ",[888,1897,1898],{},"UTC_TIMESTAMP()"," and sets ",[888,1901,1902],{},"proxy_allowed = 1"," and ",[888,1905,1906],{},"hosting_allowed = 1"," on the user's visitor record. This grants the verified device a trusted status.",[905,1909,1911],{"id":1910},"fingerprint-update","Fingerprint update",[856,1913,940,1914,1917],{},[888,1915,1916],{},"updateVisitors"," to refresh the user's device fingerprint data with the current request metadata.",[905,1919,1921],{"id":1920},"token-rotation","Token rotation",[856,1923,1924,1925,1928,1929,1931],{},"The old refresh token is verified and revoked. If ",[888,1926,1927],{},"revokeAllTokensOnSuccess"," is ",[888,1930,1326],{}," (used by the email update flow), all of the user's refresh tokens are revoked instead of just the current one. New access and refresh tokens are generated with updated claims.",[905,1933,1935],{"id":1934},"anomaly-cache-delete","Anomaly Cache delete",[856,1937,1938,1939,1942],{},"Deletes the cache entry from ",[888,1940,1941],{},"anomaliesCache"," of the anomaly that triggered this flow.",[905,1944,1946],{"id":1945},"cookie-and-response","Cookie and response",[856,1948,1949,1950,1903,1953,1956,1957,1960,1961,1928,1964,933],{},"New ",[888,1951,1952],{},"iat",[888,1954,1955],{},"session"," cookies are set on the response. The function returns ",[888,1958,1959],{},"{ accessToken, accessIat }"," by default, or the full token metadata if ",[888,1962,1963],{},"returnMetaData",[888,1965,1326],{},[905,1967,1969],{"id":1968},"cleanup","Cleanup",[856,1971,1972],{},"All rate limiters and consecutive caches for the session are reset on success, allowing the user to proceed without accumulated penalties.",[1373,1974,1975],{},[856,1976,1977],{},"A failed verification attempt increments all three consecutive caches. If any cache exceeds its threshold, subsequent attempts are delayed or blocked even if the code is correct. The user must wait for the cache durations to expire (10 to 20 minutes) before retrying.",[877,1979],{},[880,1981,1983],{"id":1982},"custom-mfa-flows","Custom MFA flows",[856,1985,1986],{},"Custom MFA flows let you protect any sensitive action behind a second-factor check. The user must be authenticated (valid access and refresh tokens) to initiate a custom flow. Unlike adaptive MFA, which triggers automatically, custom flows are initiated by your application through a service-to-service call.",[995,1988,1990],{"id":1989},"reserved-reasons","Reserved reasons",[856,1992,1993],{},"The following reason strings are reserved for internal flows and cannot be used with the custom MFA API:",[1301,1995,1996,2006],{},[1304,1997,1998],{},[1307,1999,2000,2003],{},[1310,2001,2002],{},"Reason",[1310,2004,2005],{},"Internal flow",[1317,2007,2008,2017,2027,2037],{},[1307,2009,2010,2014],{},[1322,2011,2012],{},[888,2013,928],{},[1322,2015,2016],{},"Adaptive MFA",[1307,2018,2019,2024],{},[1322,2020,2021],{},[888,2022,2023],{},"PASSWORD_RESET",[1322,2025,2026],{},"Password reset link",[1307,2028,2029,2034],{},[1322,2030,2031],{},[888,2032,2033],{},"PASSWORD_RESET_FLOW",[1322,2035,2036],{},"Password reset verification",[1307,2038,2039,2044],{},[1322,2040,2041],{},[888,2042,2043],{},"EMAIL_MFA_FLOW",[1322,2045,2046],{},"Email update verification",[995,2048,2050],{"id":2049},"initiating-a-custom-flow","Initiating a custom flow",[856,2052,2053,2056,2057,2060,2061,2064],{},[888,2054,2055],{},"POST \u002Fcustom\u002Fmfa\u002F:reason"," initiates a custom MFA flow. The controller enforces IP restrictions (the request must originate from ",[888,2058,2059],{},"service.clientIp"," or ",[888,2062,2063],{},"service.proxy.ipToTrust","), applies four layers of rate limiting, and validates the parameters with Zod.",[856,2066,1547,2067,2070,2071,2074],{},[888,2068,2069],{},"reason"," comes from the URL path parameter and ",[888,2072,2073],{},"random"," comes from the query string. There is no request body:",[1001,2076,2080],{"className":2077,"code":2078,"language":2079,"meta":1006,"style":1006},"language-http shiki shiki-themes light-plus light-plus dracula","POST \u002Fcustom\u002Fmfa\u002Fpayment?random=\u003C254-500 char string>\nAuthorization: Bearer \u003Caccess_token>\nCookie: session=...; canary_id=...\n","http",[888,2081,2082,2090,2101],{"__ignoreMap":1006},[1010,2083,2084,2087],{"class":1012,"line":1013},[1010,2085,2086],{"class":1016},"POST",[1010,2088,2089],{"class":1020}," \u002Fcustom\u002Fmfa\u002Fpayment?random=\u003C254-500 char string>\n",[1010,2091,2092,2096,2098],{"class":1012,"line":1044},[1010,2093,2095],{"class":2094},"scd-l","Authorization",[1010,2097,1085],{"class":1053},[1010,2099,2100],{"class":1037}," Bearer \u003Caccess_token>\n",[1010,2102,2103,2106,2108],{"class":1012,"line":1050},[1010,2104,2105],{"class":2094},"Cookie",[1010,2107,1085],{"class":1053},[1010,2109,2110],{"class":1037}," session=...; canary_id=...\n",[856,2112,2113,2114,2117],{},"The controller extracts device fingerprint metadata from the request (device, browser, location via IP geolocation), then calls ",[888,2115,2116],{},"generateCustomMfaFlow"," which:",[2119,2120,2121,2129,2135,2149,2155,2161],"ol",{},[2122,2123,2124,2125,2128],"li",{},"Generates a unique JTI (random UUID + 64 bytes of ",[888,2126,2127],{},"crypto.randomBytes",")",[2122,2130,2131,2132,2134],{},"Hashes the ",[888,2133,2073],{}," challenge with SHA-256",[2122,2136,2137,2138,2141,2142,2145,2146],{},"Signs a temporary JWT with ",[888,2139,2140],{},"purpose"," set to the reason and ",[888,2143,2144],{},"subject"," set to ",[888,2147,2148],{},"{reason}_{visitor}",[2122,2150,2151,2152],{},"Builds the verification URL using ",[888,2153,2154],{},"pathForCustomFlow",[2122,2156,2157,2158,2160],{},"Calls ",[888,2159,943],{}," to create and store the OTP",[2122,2162,2163,2164],{},"Sends the OTP email via ",[888,2165,962],{},[856,2167,2168],{},"The endpoint enforces a 3-second minimum response time to prevent timing-based user enumeration, and always returns HTTP 200 on success.",[995,2170,2172],{"id":2171},"verifying-a-custom-flow","Verifying a custom flow",[856,2174,2175],{},"After the user clicks the email link, two routes handle verification:",[1301,2177,2178,2190],{},[1304,2179,2180],{},[1307,2181,2182,2185,2188],{},[1310,2183,2184],{},"Route",[1310,2186,2187],{},"Method",[1310,2189,1580],{},[1317,2191,2192,2209],{},[1307,2193,2194,2199,2202],{},[1322,2195,2196],{},[888,2197,2198],{},"\u002Fauth\u002Fverify-custom-mfa",[1322,2200,2201],{},"GET",[1322,2203,2204,2205,2208],{},"Validates the signed link token via ",[888,2206,2207],{},"customMfaFlowsVerification"," middleware",[1307,2210,2211,2215,2217],{},[1322,2212,2213],{},[888,2214,2198],{},[1322,2216,2086],{},[1322,2218,2219,2220,2223,2224,1518,2226],{},"Accepts ",[888,2221,2222],{},"{ \"code\": \"1234567\" }"," and delegates to ",[888,2225,988],{},[888,2227,2228],{},"returnMetaData: true",[856,2230,2231,2232,2234],{},"The POST handler is a thin wrapper: it checks for JSON content type, extracts the code from the request body, and passes it to ",[888,2233,988],{},". On success, the response includes the full token metadata (access token, refresh token, IAT) so the calling application can update its session.",[995,2236,2238],{"id":2237},"wiring-custom-mfa-into-routes","Wiring custom MFA into routes",[856,2240,2241],{},"Import the verification middleware and controller to protect any route with a custom MFA check:",[1001,2243,2245],{"className":1003,"code":2244,"language":1005,"meta":1006,"style":1006},"import {\n  customMfaFlowsVerification,\n  initCustomMfaFlow,\n  verifyCustomMfa,\n} from '@riavzon\u002Fauth'\n",[888,2246,2247,2254,2261,2268,2275],{"__ignoreMap":1006},[1010,2248,2249,2251],{"class":1012,"line":1013},[1010,2250,1017],{"class":1016},[1010,2252,2253],{"class":1020}," {\n",[1010,2255,2256,2259],{"class":1012,"line":1044},[1010,2257,2258],{"class":1024},"  customMfaFlowsVerification",[1010,2260,1120],{"class":1020},[1010,2262,2263,2266],{"class":1012,"line":1050},[1010,2264,2265],{"class":1024},"  initCustomMfaFlow",[1010,2267,1120],{"class":1020},[1010,2269,2270,2273],{"class":1012,"line":1075},[1010,2271,2272],{"class":1024},"  verifyCustomMfa",[1010,2274,1120],{"class":1020},[1010,2276,2277,2280,2282,2284,2286],{"class":1012,"line":1114},[1010,2278,2279],{"class":1020},"} ",[1010,2281,1030],{"class":1016},[1010,2283,1034],{"class":1033},[1010,2285,1038],{"class":1037},[1010,2287,1041],{"class":1033},[2289,2290,2291],"tip",{},[856,2292,2293,2294,2296,2297,2299],{},"The custom MFA flow uses the same ",[888,2295,951],{}," table, the same ",[888,2298,988],{}," function, and the same rate limiters as adaptive MFA. The only differences are the JWT purpose claim, the URL path, and the IP restriction on initiation.",[877,2301],{},[880,2303,2305],{"id":2304},"email-update-flow","Email update flow",[856,2307,2308],{},"The email update flow is a specialized use of the MFA system that requires the user to verify their identity with both their current password and a valid OTP code before their email address is changed.",[901,2310,2311,2315,2322,2326,2329,2424,2428,2435,2439,2445,2459,2463],{"level":903},[905,2312,2314],{"id":2313},"initiation","Initiation",[856,2316,2317,2318,2321],{},"The client application initiates a custom MFA flow with purpose ",[888,2319,2320],{},"change_email"," or uses the built-in email MFA flow, sending the user a verification email to their current address.",[905,2323,2325],{"id":2324},"submission","Submission",[856,2327,2328],{},"The user submits a request with three fields:",[1001,2330,2332],{"className":1003,"code":2331,"language":1005,"meta":1006,"style":1006},"{\n  \"email\": \"current@example.com\",\n  \"newEmail\": \"new@example.com\",\n  \"password\": \"current-password\",\n  \"code\": \"1234567\"\n}\n",[888,2333,2334,2339,2362,2382,2402,2420],{"__ignoreMap":1006},[1010,2335,2336],{"class":1012,"line":1013},[1010,2337,2338],{"class":1020},"{\n",[1010,2340,2341,2344,2347,2350,2353,2355,2358,2360],{"class":1012,"line":1044},[1010,2342,2343],{"class":1033},"  \"",[1010,2345,2346],{"class":1037},"email",[1010,2348,2349],{"class":1033},"\"",[1010,2351,2352],{"class":1020},": ",[1010,2354,2349],{"class":1033},[1010,2356,2357],{"class":1037},"current@example.com",[1010,2359,2349],{"class":1033},[1010,2361,1120],{"class":1020},[1010,2363,2364,2366,2369,2371,2373,2375,2378,2380],{"class":1012,"line":1050},[1010,2365,2343],{"class":1033},[1010,2367,2368],{"class":1037},"newEmail",[1010,2370,2349],{"class":1033},[1010,2372,2352],{"class":1020},[1010,2374,2349],{"class":1033},[1010,2376,2377],{"class":1037},"new@example.com",[1010,2379,2349],{"class":1033},[1010,2381,1120],{"class":1020},[1010,2383,2384,2386,2389,2391,2393,2395,2398,2400],{"class":1012,"line":1075},[1010,2385,2343],{"class":1033},[1010,2387,2388],{"class":1037},"password",[1010,2390,2349],{"class":1033},[1010,2392,2352],{"class":1020},[1010,2394,2349],{"class":1033},[1010,2396,2397],{"class":1037},"current-password",[1010,2399,2349],{"class":1033},[1010,2401,1120],{"class":1020},[1010,2403,2404,2406,2408,2410,2412,2414,2417],{"class":1012,"line":1114},[1010,2405,2343],{"class":1033},[1010,2407,888],{"class":1037},[1010,2409,2349],{"class":1033},[1010,2411,2352],{"class":1020},[1010,2413,2349],{"class":1033},[1010,2415,2416],{"class":1037},"1234567",[1010,2418,2419],{"class":1033},"\"\n",[1010,2421,2422],{"class":1012,"line":1123},[1010,2423,1296],{"class":1020},[905,2425,2427],{"id":2426},"password-verification","Password verification",[856,2429,2430,2431,2434],{},"The controller validates the request with Zod, confirms that ",[888,2432,2433],{},"req.link.purpose === 'change_email'",", and verifies the current password against the stored Argon2 hash.",[905,2436,2438],{"id":2437},"mfa-verification-with-callback","MFA verification with callback",[856,2440,2441,2442,2444],{},"The controller delegates to ",[888,2443,988],{}," with two special options:",[2446,2447,2448,2454],"ul",{},[2122,2449,2450,2453],{},[888,2451,2452],{},"revokeAllTokensOnSuccess: true"," - All of the user's refresh tokens are revoked (not just the current one), forcing re-authentication on every device",[2122,2455,2456,2458],{},[888,2457,1883],{}," callback - Executes inside the verification transaction to update the user's email address in the database",[905,2460,2462],{"id":2461},"notification","Notification",[856,2464,2465,2466,2469,2470,2473],{},"After successful verification and email change, the service sends a notification email to the ",[860,2467,2468],{},"old"," email address via ",[888,2471,2472],{},"sendEmailNotification"," with the subject \"Your Email Has Changed\", alerting the user in case the change was unauthorized.",[1373,2475,2476],{},[856,2477,2478],{},"Revoking all tokens on email change is a deliberate security measure. If an attacker gained access to the account and changed the email, the legitimate user's sessions on other devices are invalidated, and the notification to the old address serves as an alert.",[877,2480],{},[880,2482,2484],{"id":2483},"post-mfa-bypass-window","Post-MFA bypass window",[856,2486,2487,2488,2490,2491,2493,2494,2497],{},"After a successful MFA verification, the service records ",[888,2489,992],{}," on the user record with ",[888,2492,1898],{},". For the duration configured in ",[888,2495,2496],{},"byPassAnomaliesFor",", anomaly checks that would normally require MFA (such as exceeding the session limit or connecting from a new IP) are skipped. This prevents users from being immediately challenged again on a device they have just verified.",[856,2499,2500,2501,1903,2503,2505],{},"The bypass also sets ",[888,2502,1902],{},[888,2504,1906],{}," on the user's visitor record, granting trusted status to connections from proxies or hosting providers that would otherwise trigger anomaly detection.",[877,2507],{},[880,2509,2511],{"id":2510},"routes-reference","Routes reference",[1301,2513,2514,2524],{},[1304,2515,2516],{},[1307,2517,2518,2520,2522],{},[1310,2519,2184],{},[1310,2521,2187],{},[1310,2523,1580],{},[1317,2525,2526,2538,2549,2561,2572,2583,2595,2607,2619],{},[1307,2527,2528,2533,2535],{},[1322,2529,2530],{},[888,2531,2532],{},"\u002Fauth\u002Fverify-mfa",[1322,2534,2201],{},[1322,2536,2537],{},"Validates the adaptive MFA magic link token",[1307,2539,2540,2544,2546],{},[1322,2541,2542],{},[888,2543,2532],{},[1322,2545,2086],{},[1322,2547,2548],{},"Submits OTP code for adaptive MFA verification",[1307,2550,2551,2556,2558],{},[1322,2552,2553],{},[888,2554,2555],{},"\u002Fcustom\u002Fmfa\u002F:reason",[1322,2557,2086],{},[1322,2559,2560],{},"Initiates a custom MFA flow (service-to-service, IP restricted)",[1307,2562,2563,2567,2569],{},[1322,2564,2565],{},[888,2566,2198],{},[1322,2568,2201],{},[1322,2570,2571],{},"Validates the custom MFA magic link token",[1307,2573,2574,2578,2580],{},[1322,2575,2576],{},[888,2577,2198],{},[1322,2579,2086],{},[1322,2581,2582],{},"Submits OTP code for custom MFA verification",[1307,2584,2585,2590,2592],{},[1322,2586,2587],{},[888,2588,2589],{},"\u002Fupdate\u002Femail",[1322,2591,2086],{},[1322,2593,2594],{},"Submits new email + password + OTP for email change",[1307,2596,2597,2602,2604],{},[1322,2598,2599],{},[888,2600,2601],{},"\u002Fauth\u002Fforgot-password",[1322,2603,2086],{},[1322,2605,2606],{},"Initiates a password reset email",[1307,2608,2609,2614,2616],{},[1322,2610,2611],{},[888,2612,2613],{},"\u002Fauth\u002Freset-password",[1322,2615,2201],{},[1322,2617,2618],{},"Validates the password reset magic link",[1307,2620,2621,2625,2627],{},[1322,2622,2623],{},[888,2624,2613],{},[1322,2626,2086],{},[1322,2628,2629],{},"Submits the new password after link validation",[877,2631],{},[880,2633,2635],{"id":2634},"configuration-reference","Configuration reference",[995,2637,2639],{"id":2638},"otp-settings","OTP settings",[2641,2642,2643,2662],"field-group",{},[2644,2645,2649],"field",{"name":2646,"type":2647,"default":2648},"mfa.codeLength","number","7",[856,2650,2651,2652,2655,2656,1895,2659,933],{},"The number of digits in the OTP code. Generated via ",[888,2653,2654],{},"crypto.randomInt"," with range ",[888,2657,2658],{},"1_000_000",[888,2660,2661],{},"9_999_999",[2644,2663,2666],{"name":2664,"type":2647,"default":2665},"mfa.codeExpiry","420000",[856,2667,2668],{},"Time in milliseconds before an OTP code expires. Default is 7 minutes (420,000 ms).",[995,2670,2672],{"id":2671},"bypass-window","Bypass window",[2641,2674,2675],{},[2644,2676,2679],{"name":2677,"type":2647,"default":2678},"jwt.refresh_tokens.byPassAnomaliesFor","300000",[856,2680,2681],{},"Duration in milliseconds after a successful MFA verification during which anomaly checks are skipped for the verified visitor. Default is 5 minutes (300,000 ms).",[995,2683,2685],{"id":2684},"rate-limiters","Rate limiters",[2641,2687,2688,2698,2707,2713,2725],{},[2644,2689,2692],{"name":2690,"type":2691},"globalEmailLimiter","RateLimiterMySQL",[856,2693,2694,2695,2697],{},"Global rate limiter for all outbound MFA emails across all users. Keyed by ",[888,2696,1382],{},". Prevents email-sending abuse at the system level.",[2644,2699,2701],{"name":2700,"type":2691},"userIdLimiter",[856,2702,2703,2704,2706],{},"Per-user rate limiter for MFA email requests. Keyed by ",[888,2705,1386],{},". Prevents a single user from triggering excessive emails.",[2644,2708,2710],{"name":2709,"type":2691},"ipLimiter",[856,2711,2712],{},"Per-IP rate limiter for MFA email and verification requests. Prevents a single IP from flooding the system.",[2644,2714,2715],{"name":1704,"type":2691},[856,2716,2717,2718,2720,2721,2724],{},"Composite rate limiter keyed by ",[888,2719,1709],{}," for verification attempts, or ",[888,2722,2723],{},"{IP}_{random}_{reason}"," for custom flow initiation. The most granular limiter.",[2644,2726,2728],{"name":2727,"type":2691},"emailLimiter",[856,2729,2730,2731,2734],{},"Keyed by ",[888,2732,2733],{},"{random}_{reason}"," for custom flows. Limits initiation attempts per challenge and reason pair.",[995,2736,2738],{"id":2737},"email-images","Email images",[856,2740,2741,2742,2745,2746,2750],{},"Image URLs for the OTP email template. Configured under ",[888,2743,2744],{},"magic_links.emailImages.otp",". See the ",[865,2747,2749],{"href":2748},"\u002Fdocs\u002Fiam\u002Fessentials\u002Femails#email-images","emails configuration reference"," for the full list of image fields.",[2752,2753,2754],"style",{},"html pre.shiki code .sZ328, html code.shiki .sZ328{--shiki-light:#AF00DB;--shiki-default:#AF00DB;--shiki-dark:#FF79C6}html pre.shiki code .sDd4n, html code.shiki .sDd4n{--shiki-light:#000000;--shiki-default:#000000;--shiki-dark:#F8F8F2}html pre.shiki code .sjsA6, html code.shiki .sjsA6{--shiki-light:#001080;--shiki-default:#001080;--shiki-dark:#F8F8F2}html pre.shiki code .sFkSl, html code.shiki .sFkSl{--shiki-light:#A31515;--shiki-default:#A31515;--shiki-dark:#E9F284}html pre.shiki code .sFB1V, html code.shiki .sFB1V{--shiki-light:#A31515;--shiki-default:#A31515;--shiki-dark:#F1FA8C}html pre.shiki code .sl46w, html code.shiki .sl46w{--shiki-light:#0000FF;--shiki-default:#0000FF;--shiki-dark:#FF79C6}html pre.shiki code .s3JHE, html code.shiki .s3JHE{--shiki-light:#0070C1;--shiki-default:#0070C1;--shiki-dark:#F8F8F2}html pre.shiki code .saOXh, html code.shiki .saOXh{--shiki-light:#000000;--shiki-default:#000000;--shiki-dark:#FF79C6}html pre.shiki code .sHOzp, html code.shiki .sHOzp{--shiki-light:#795E26;--shiki-default:#795E26;--shiki-dark:#50FA7B}html pre.shiki code .s34zl, html code.shiki .s34zl{--shiki-light:#001080;--shiki-default:#001080;--shiki-dark:#FF79C6}html pre.shiki code .spgvN, html code.shiki .spgvN{--shiki-light:#098658;--shiki-default:#098658;--shiki-dark:#BD93F9}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sghk6, html code.shiki .sghk6{--shiki-light:#008000;--shiki-default:#008000;--shiki-dark:#6272A4}html pre.shiki code .scd-l, html code.shiki .scd-l{--shiki-light:#800000;--shiki-default:#800000;--shiki-dark:#FF79C6}",{"title":1006,"searchDepth":1044,"depth":1044,"links":2756},[2757,2760,2764,2768,2774,2775,2776,2777],{"id":882,"depth":1044,"text":883,"children":2758},[2759],{"id":997,"depth":1050,"text":898},{"id":1395,"depth":1044,"text":937,"children":2761},[2762],{"id":1546,"depth":1050,"text":2763},"The mfa_codes table",{"id":1668,"depth":1044,"text":1669,"children":2765},[2766,2767],{"id":1677,"depth":1050,"text":1678},{"id":1814,"depth":1050,"text":1815},{"id":1982,"depth":1044,"text":1983,"children":2769},[2770,2771,2772,2773],{"id":1989,"depth":1050,"text":1990},{"id":2049,"depth":1050,"text":2050},{"id":2171,"depth":1050,"text":2172},{"id":2237,"depth":1050,"text":2238},{"id":2304,"depth":1044,"text":2305},{"id":2483,"depth":1044,"text":2484},{"id":2510,"depth":1044,"text":2511},{"id":2634,"depth":1044,"text":2635,"children":2778},[2779,2780,2781,2782],{"id":2638,"depth":1050,"text":2639},{"id":2671,"depth":1050,"text":2672},{"id":2684,"depth":1050,"text":2685},{"id":2737,"depth":1050,"text":2738},"Email OTP adaptive MFA triggered by anomaly detection, custom MFA flows for sensitive actions, OTP code generation and verification, and the full lifecycle in the IAM service.","md","i-lucide-shield-alert",{},null,"---\ntitle: MFA\ndescription: Email OTP adaptive MFA triggered by anomaly detection, custom MFA flows for sensitive actions, OTP code generation and verification, and the full lifecycle in the IAM service.\nicon: i-lucide-shield-alert\n---\n\nThe IAM service provides two MFA mechanisms: **adaptive MFA** triggered automatically by [anomaly detection](\u002Fdocs\u002Fiam\u002Fessentials\u002Fanomalies) when a suspicious session event occurs, and **custom MFA flows** that your application triggers explicitly for sensitive user actions such as changing a password, transferring funds, or deleting an account.\n\nBoth mechanisms share the same infrastructure: a signed temporary JWT link, a 7-digit OTP code stored as a SHA-256 hash with a 7-minute expiry, multi-layer rate limiting, and an atomic verification function that rotates tokens on success.\n\n---\n\n## Adaptive MFA lifecycle\n\nWhen `strangeThings()` determines that a session event is suspicious (IP change, device mismatch, session limit exceeded, or proxy\u002Fhosting anomaly), it returns `reqMFA: true`. The calling controller then invokes `sendTempMfaLink`, which triggers the full adaptive MFA flow.\n\n::steps{level=\"4\"}\n#### Link generation\n\n`sendTempMfaLink` generates a unique JTI (`crypto.randomUUID()` + `crypto.randomBytes(64).toString('hex')`, producing a 164-character string), a random challenge (`crypto.randomBytes(128)`), and hashes the challenge with SHA-256. It then signs a temporary JWT with purpose `MAGIC_LINK_MFA_CHECKS` and a subject of `MAGIC_LINK_MFA_CHECKS_{visitor}`.\n\n#### OTP generation\n\nThe function calls `generateMfaCode` (described in [OTP generation](#otp-generation)) to create a 7-digit code, hash it, and store it in the `mfa_codes` table tied to the user's current refresh token.\n\n#### Email delivery\n\nThe function fetches the user's email and display name from the database, then calls `mfaEmail` to render the [OTP email template](\u002Fdocs\u002Fiam\u002Fessentials\u002Femails#otp-template) with the code, magic link URL, and contextual metadata (device, browser, location). The email subject is formatted as `Security Code - {code}`.\n\n#### User interaction\n\nThe user receives an email with the 7-digit code displayed prominently and a \"Verify Here\" button linking to the magic link URL. They can either click the link (which pre-fills the code through the bounce page) or copy the code manually into the verification form.\n\n#### Verification\n\nThe user submits the code to the MFA verification route. `verifyMfaCode` validates the code, rotates the user's tokens, updates their fingerprints, and records `last_mfa_at` on the user record. The flow completes with new access and refresh tokens.\n::\n\n### `sendTempMfaLink`\n\n```ts\nimport { sendTempMfaLink } from '@riavzon\u002Fauth'\n\nconst result = await sendTempMfaLink(\n  { userId: user.id, visitor: user.visitor_id },\n  rawRefreshToken,\n  req.ip!,\n  res,\n  {\n    device: 'Safari on iOS',\n    browser: 'Safari 17',\n    location: 'Paris, FR',\n  }\n)\n\nif (result === 'rate_limited') {\n  res.status(429).json({ error: 'Too many MFA requests. Try again later.' })\n  return\n}\n```\n\nThe function returns one of three values:\n\n| Return value | Meaning |\n|---|---|\n| `true` | Email sent successfully |\n| `false` | An error occurred during code generation or email delivery |\n| `'rate_limited'` | One of the three rate limiters rejected the request |\n\nThe metadata parameter accepts the `EmailMetaDataOTP` shape with three fields: `device`, `browser`, and `location`. The `date` field shown in the email template is generated server-side automatically.\n\n::warning\n`sendTempMfaLink` applies three independent rate limiters before sending: a global email limiter (keyed `'global_emails'`), a per-user limiter (keyed `'user_{userId}'`), and a per-IP limiter. If any one of them rejects the request, the function returns `'rate_limited'` without sending.\n::\n\n---\n\n## OTP generation\n\n`generateMfaCode` produces a cryptographically secure 7-digit numeric code using `crypto.randomInt(1_000_000, 9_999_999)`, zero-padded to 7 characters. The raw code is returned to the caller for email delivery, while a SHA-256 hash of the code is stored in the database.\n\n```ts\nimport { generateMfaCode } from '@riavzon\u002Fauth'\n\nconst result = await generateMfaCode(log, sessionToken, userId, jti)\n\nif (result.ok) {\n  \u002F\u002F result.code  — the raw 7-digit string, send to the user\n  \u002F\u002F result.data  — insertion metadata\n}\n```\n\nThe function uses a database transaction with idempotency logic:\n\n::steps{level=\"4\"}\n#### Check for existing code\n\nBefore generating a new code, the function queries the `mfa_codes` table for an unexpired, unused code belonging to the same user. If one exists, it skips insertion and returns `ok: true` with `code: undefined` to signal that a valid code is already in flight.\n\n#### Clean up old codes\n\nAny expired or previously used codes for the user are deleted from the table.\n\n#### Insert new code\n\nA new row is inserted with the SHA-256 hashed code, the hashed session token, the JTI, the user ID, and an expiry timestamp set to 7 minutes from now. The `used` column defaults to `0`.\n::\n\n### The `mfa_codes` table\n\nEach row in the `mfa_codes` table represents a single pending OTP challenge. The table enforces uniqueness on `user_id`, `code_hash`, `jti`, and `token`, meaning each user can have at most one active code at any time.\n\n| Column | Purpose |\n|---|---|\n| `user_id` | The user the code belongs to. Foreign key to `users.id` with `CASCADE` delete. |\n| `token` | The hashed refresh token that was active when the code was issued. Foreign key to `refresh_tokens.token` with `CASCADE` delete. If the token is revoked, the code is automatically invalidated. |\n| `jti` | The JWT ID from the temporary link. Uniquely identifies the verification session. |\n| `code_hash` | SHA-256 hash of the 7-digit code. Never stores the raw code. |\n| `expires_at` | UTC timestamp. The code is invalid after this time (7 minutes from creation). |\n| `used` | Boolean flag. Set to `0` on creation, never updated (codes are deleted on use, not marked used). |\n\n::warning\nThe `token` foreign key with `CASCADE` delete means revoking a user's refresh token (through logout, token rotation, or admin action) automatically invalidates any pending MFA code tied to that token.\n::\n\n---\n\n## OTP verification\n\n`verifyMfaCode` is the central verification function used by all MFA flows: adaptive MFA, custom MFA, and email update. It validates the submitted code, performs the database lookup, and on success rotates tokens, updates fingerprints, and records the verification timestamp.\n\n### Rate limiting\n\nVerification is protected by four rate limiters and three consecutive-failure caches that work together to prevent brute-force attacks:\n\n| Limiter | Key | Purpose |\n|---|---|---|\n| `uniLimiter` | `{IP}_{JTI}` | Limits attempts per IP and verification session |\n| `ipLimit` | `{code_hash}` | Blocks a specific code hash after verification (prevents replay) |\n| `usedJtiLimiter` | `{JTI}` | Block-only limiter that prevents reuse of a verified JTI |\n\n| Consecutive cache | Key | Duration | Purpose |\n|---|---|---|---|\n| `consecutiveForSubmittedHash` | `{code_hash}` | 10 minutes | Tracks repeated submissions of the same wrong code |\n| `consecutiveForSlowDown` | `{IP}` | 10 minutes | Progressive slowdown on repeated failures from same IP |\n| `consecutiveForJti` | `{JTI}` | 20 minutes | Tracks failures per verification session |\n\n### Verification flow\n\n::steps\n---\nlevel: 4\n---\n#### Input validation\n\nThe submitted code is validated with a Zod schema to ensure it matches the expected format before any database query runs.\n\n#### Hash and lookup\n\nThe submitted code is SHA-256 hashed. The function queries the `mfa_codes` table for a row matching the JTI, the code hash, an `expires_at` in the future, and `used = 0`. The query uses `FOR UPDATE` to lock the row.\n\n#### Atomic delete\n\nIf a matching row is found, it is deleted with a `LIMIT 1` constraint. The function checks that `affectedRows === 1` to confirm the delete was atomic. This prevents time-of-check\u002Ftime-of-use (TOCTOU) races where two concurrent requests could both verify the same code.\n\n#### Post-verification blocking\n\nThe verified code hash is blocked in `ipLimit` for 10 minutes, and the JTI is blocked in `usedJtiLimiter` for 20 minutes. This prevents replay of the same code or reuse of the verification session.\n\n#### Optional callback\n\nIf the caller passed an `onSuccess` callback (used by the email update flow to change the email address), it executes inside the same database transaction. If the callback throws, the entire transaction rolls back and the code is not consumed.\n\n#### User record update\n\nThe function updates `users.last_mfa_at` to `UTC_TIMESTAMP()` and sets `proxy_allowed = 1` and `hosting_allowed = 1` on the user's visitor record. This grants the verified device a trusted status.\n\n#### Fingerprint update\n\nThe function calls `updateVisitors` to refresh the user's device fingerprint data with the current request metadata.\n\n#### Token rotation\n\nThe old refresh token is verified and revoked. If `revokeAllTokensOnSuccess` is `true` (used by the email update flow), all of the user's refresh tokens are revoked instead of just the current one. New access and refresh tokens are generated with updated claims.\n\n#### Anomaly Cache delete\n\nDeletes the cache entry from `anomaliesCache` of the anomaly that triggered this flow.\n\n#### Cookie and response\n\nNew `iat` and `session` cookies are set on the response. The function returns `{ accessToken, accessIat }` by default, or the full token metadata if `returnMetaData` is `true`.\n\n#### Cleanup\n\nAll rate limiters and consecutive caches for the session are reset on success, allowing the user to proceed without accumulated penalties.\n::\n\n::warning\nA failed verification attempt increments all three consecutive caches. If any cache exceeds its threshold, subsequent attempts are delayed or blocked even if the code is correct. The user must wait for the cache durations to expire (10 to 20 minutes) before retrying.\n::\n\n---\n\n## Custom MFA flows\n\nCustom MFA flows let you protect any sensitive action behind a second-factor check. The user must be authenticated (valid access and refresh tokens) to initiate a custom flow. Unlike adaptive MFA, which triggers automatically, custom flows are initiated by your application through a service-to-service call.\n\n### Reserved reasons\n\nThe following reason strings are reserved for internal flows and cannot be used with the custom MFA API:\n\n| Reason | Internal flow |\n|---|---|\n| `MAGIC_LINK_MFA_CHECKS` | Adaptive MFA |\n| `PASSWORD_RESET` | Password reset link |\n| `PASSWORD_RESET_FLOW` | Password reset verification |\n| `EMAIL_MFA_FLOW` | Email update verification |\n\n### Initiating a custom flow\n\n`POST \u002Fcustom\u002Fmfa\u002F:reason` initiates a custom MFA flow. The controller enforces IP restrictions (the request must originate from `service.clientIp` or `service.proxy.ipToTrust`), applies four layers of rate limiting, and validates the parameters with Zod.\n\nThe `reason` comes from the URL path parameter and `random` comes from the query string. There is no request body:\n\n```http\nPOST \u002Fcustom\u002Fmfa\u002Fpayment?random=\u003C254-500 char string>\nAuthorization: Bearer \u003Caccess_token>\nCookie: session=...; canary_id=...\n```\n\nThe controller extracts device fingerprint metadata from the request (device, browser, location via IP geolocation), then calls `generateCustomMfaFlow` which:\n\n1. Generates a unique JTI (random UUID + 64 bytes of `crypto.randomBytes`)\n2. Hashes the `random` challenge with SHA-256\n3. Signs a temporary JWT with `purpose` set to the reason and `subject` set to `{reason}_{visitor}`\n4. Builds the verification URL using `pathForCustomFlow`\n5. Calls `generateMfaCode` to create and store the OTP\n6. Sends the OTP email via `mfaEmail`\n\nThe endpoint enforces a 3-second minimum response time to prevent timing-based user enumeration, and always returns HTTP 200 on success.\n\n### Verifying a custom flow\n\nAfter the user clicks the email link, two routes handle verification:\n\n| Route | Method | Purpose |\n|---|---|---|\n| `\u002Fauth\u002Fverify-custom-mfa` | GET | Validates the signed link token via `customMfaFlowsVerification` middleware |\n| `\u002Fauth\u002Fverify-custom-mfa` | POST | Accepts `{ \"code\": \"1234567\" }` and delegates to `verifyMfaCode` with `returnMetaData: true` |\n\nThe POST handler is a thin wrapper: it checks for JSON content type, extracts the code from the request body, and passes it to `verifyMfaCode`. On success, the response includes the full token metadata (access token, refresh token, IAT) so the calling application can update its session.\n\n### Wiring custom MFA into routes\n\nImport the verification middleware and controller to protect any route with a custom MFA check:\n\n```ts\nimport {\n  customMfaFlowsVerification,\n  initCustomMfaFlow,\n  verifyCustomMfa,\n} from '@riavzon\u002Fauth'\n```\n\n::tip\nThe custom MFA flow uses the same `mfa_codes` table, the same `verifyMfaCode` function, and the same rate limiters as adaptive MFA. The only differences are the JWT purpose claim, the URL path, and the IP restriction on initiation.\n::\n\n---\n\n## Email update flow\n\nThe email update flow is a specialized use of the MFA system that requires the user to verify their identity with both their current password and a valid OTP code before their email address is changed.\n\n::steps{level=\"4\"}\n#### Initiation\n\nThe client application initiates a custom MFA flow with purpose `change_email` or uses the built-in email MFA flow, sending the user a verification email to their current address.\n\n#### Submission\n\nThe user submits a request with three fields:\n\n```ts\n{\n  \"email\": \"current@example.com\",\n  \"newEmail\": \"new@example.com\",\n  \"password\": \"current-password\",\n  \"code\": \"1234567\"\n}\n```\n\n#### Password verification\n\nThe controller validates the request with Zod, confirms that `req.link.purpose === 'change_email'`, and verifies the current password against the stored Argon2 hash.\n\n#### MFA verification with callback\n\nThe controller delegates to `verifyMfaCode` with two special options:\n- `revokeAllTokensOnSuccess: true` - All of the user's refresh tokens are revoked (not just the current one), forcing re-authentication on every device\n- `onSuccess` callback - Executes inside the verification transaction to update the user's email address in the database\n\n#### Notification\n\nAfter successful verification and email change, the service sends a notification email to the **old** email address via `sendEmailNotification` with the subject \"Your Email Has Changed\", alerting the user in case the change was unauthorized.\n::\n\n::warning\nRevoking all tokens on email change is a deliberate security measure. If an attacker gained access to the account and changed the email, the legitimate user's sessions on other devices are invalidated, and the notification to the old address serves as an alert.\n::\n\n---\n\n## Post-MFA bypass window\n\nAfter a successful MFA verification, the service records `last_mfa_at` on the user record with `UTC_TIMESTAMP()`. For the duration configured in `byPassAnomaliesFor`, anomaly checks that would normally require MFA (such as exceeding the session limit or connecting from a new IP) are skipped. This prevents users from being immediately challenged again on a device they have just verified.\n\nThe bypass also sets `proxy_allowed = 1` and `hosting_allowed = 1` on the user's visitor record, granting trusted status to connections from proxies or hosting providers that would otherwise trigger anomaly detection.\n\n---\n\n## Routes reference\n\n| Route | Method | Purpose |\n|---|---|---|\n| `\u002Fauth\u002Fverify-mfa` | GET | Validates the adaptive MFA magic link token |\n| `\u002Fauth\u002Fverify-mfa` | POST | Submits OTP code for adaptive MFA verification |\n| `\u002Fcustom\u002Fmfa\u002F:reason` | POST | Initiates a custom MFA flow (service-to-service, IP restricted) |\n| `\u002Fauth\u002Fverify-custom-mfa` | GET | Validates the custom MFA magic link token |\n| `\u002Fauth\u002Fverify-custom-mfa` | POST | Submits OTP code for custom MFA verification |\n| `\u002Fupdate\u002Femail` | POST | Submits new email + password + OTP for email change |\n| `\u002Fauth\u002Fforgot-password` | POST | Initiates a password reset email |\n| `\u002Fauth\u002Freset-password` | GET | Validates the password reset magic link |\n| `\u002Fauth\u002Freset-password` | POST | Submits the new password after link validation |\n\n---\n\n## Configuration reference\n\n### OTP settings\n\n::field-group\n::field{name=\"mfa.codeLength\" type=\"number\" default=\"7\"}\nThe number of digits in the OTP code. Generated via `crypto.randomInt` with range `1_000_000` to `9_999_999`.\n::\n\n::field{name=\"mfa.codeExpiry\" type=\"number\" default=\"420000\"}\nTime in milliseconds before an OTP code expires. Default is 7 minutes (420,000 ms).\n::\n::\n\n### Bypass window\n\n::field-group\n::field{name=\"jwt.refresh_tokens.byPassAnomaliesFor\" type=\"number\" default=\"300000\"}\nDuration in milliseconds after a successful MFA verification during which anomaly checks are skipped for the verified visitor. Default is 5 minutes (300,000 ms).\n::\n::\n\n### Rate limiters\n\n::field-group\n::field{name=\"globalEmailLimiter\" type=\"RateLimiterMySQL\"}\nGlobal rate limiter for all outbound MFA emails across all users. Keyed by `'global_emails'`. Prevents email-sending abuse at the system level.\n::\n\n::field{name=\"userIdLimiter\" type=\"RateLimiterMySQL\"}\nPer-user rate limiter for MFA email requests. Keyed by `'user_{userId}'`. Prevents a single user from triggering excessive emails.\n::\n\n::field{name=\"ipLimiter\" type=\"RateLimiterMySQL\"}\nPer-IP rate limiter for MFA email and verification requests. Prevents a single IP from flooding the system.\n::\n\n::field{name=\"uniLimiter\" type=\"RateLimiterMySQL\"}\nComposite rate limiter keyed by `{IP}_{JTI}` for verification attempts, or `{IP}_{random}_{reason}` for custom flow initiation. The most granular limiter.\n::\n\n::field{name=\"emailLimiter\" type=\"RateLimiterMySQL\"}\nKeyed by `{random}_{reason}` for custom flows. Limits initiation attempts per challenge and reason pair.\n::\n::\n\n### Email images\n\nImage URLs for the OTP email template. Configured under `magic_links.emailImages.otp`. See the [emails configuration reference](\u002Fdocs\u002Fiam\u002Fessentials\u002Femails#email-images) for the full list of image fields.\n",{"title":123,"description":2783},"gq9gM8JLAKaOXhTHLgmuk3_z5Nlig6piMv5MCNqsUkg",[2792,2793],{"title":119,"path":120,"stem":121,"children":-1},{"title":127,"path":128,"stem":129,"children":-1},{"id":851,"title":123,"body":2795,"description":2783,"extension":2784,"icon":2785,"meta":4168,"module":2787,"navigation":8,"path":124,"rawbody":2788,"seo":4169,"stem":125,"__hash__":2790},{"type":853,"value":2796,"toc":4141},[2797,2805,2807,2809,2811,2819,2869,2873,3079,3081,3117,3129,3141,3143,3145,3151,3237,3239,3263,3267,3279,3349,3357,3359,3361,3365,3367,3369,3419,3477,3479,3569,3573,3575,3577,3579,3581,3583,3627,3629,3637,3643,3669,3673,3703,3705,3707,3709,3751,3755,3757,3759,3799,3807,3809,3811,3813,3937,3941,3943,3945,3953,3959,3961,3963,4067,4069,4071,4073,4089,4091,4097,4099,4131,4133,4139],[856,2798,858,2799,863,2801,868,2803,872],{},[860,2800,862],{},[865,2802,867],{"href":96},[860,2804,871],{},[856,2806,875],{},[877,2808],{},[880,2810,883],{"id":882},[856,2812,886,2813,891,2815,895,2817,899],{},[888,2814,890],{},[888,2816,894],{},[888,2818,898],{},[901,2820,2821,2823,2837,2839,2847,2849,2857,2859,2861,2863],{"level":903},[905,2822,908],{"id":907},[856,2824,2825,913,2827,917,2829,921,2831,925,2833,929,2835,933],{},[888,2826,898],{},[888,2828,916],{},[888,2830,920],{},[888,2832,924],{},[888,2834,928],{},[888,2836,932],{},[905,2838,937],{"id":936},[856,2840,940,2841,944,2843,948,2845,952],{},[888,2842,943],{},[865,2844,937],{"href":947},[888,2846,951],{},[905,2848,956],{"id":955},[856,2850,959,2851,963,2853,968,2855,933],{},[888,2852,962],{},[865,2854,967],{"href":966},[888,2856,971],{},[905,2858,975],{"id":974},[856,2860,978],{},[905,2862,982],{"id":981},[856,2864,985,2865,989,2867,993],{},[888,2866,988],{},[888,2868,992],{},[995,2870,2871],{"id":997},[888,2872,898],{},[1001,2874,2875],{"className":1003,"code":1004,"language":1005,"meta":1006,"style":1006},[888,2876,2877,2895,2899,2913,2941,2947,2959,2965,2969,2983,2997,3011,3015,3019,3023,3041,3071,3075],{"__ignoreMap":1006},[1010,2878,2879,2881,2883,2885,2887,2889,2891,2893],{"class":1012,"line":1013},[1010,2880,1017],{"class":1016},[1010,2882,1021],{"class":1020},[1010,2884,898],{"class":1024},[1010,2886,1027],{"class":1020},[1010,2888,1030],{"class":1016},[1010,2890,1034],{"class":1033},[1010,2892,1038],{"class":1037},[1010,2894,1041],{"class":1033},[1010,2896,2897],{"class":1012,"line":1044},[1010,2898,1047],{"emptyLinePlaceholder":8},[1010,2900,2901,2903,2905,2907,2909,2911],{"class":1012,"line":1050},[1010,2902,1054],{"class":1053},[1010,2904,1058],{"class":1057},[1010,2906,1062],{"class":1061},[1010,2908,1065],{"class":1016},[1010,2910,1069],{"class":1068},[1010,2912,1072],{"class":1020},[1010,2914,2915,2917,2919,2921,2923,2925,2927,2929,2931,2933,2935,2937,2939],{"class":1012,"line":1075},[1010,2916,1078],{"class":1020},[1010,2918,1081],{"class":1024},[1010,2920,1085],{"class":1084},[1010,2922,1088],{"class":1024},[1010,2924,933],{"class":1020},[1010,2926,1093],{"class":1024},[1010,2928,1096],{"class":1020},[1010,2930,1099],{"class":1024},[1010,2932,1085],{"class":1084},[1010,2934,1088],{"class":1024},[1010,2936,933],{"class":1020},[1010,2938,1108],{"class":1024},[1010,2940,1111],{"class":1020},[1010,2942,2943,2945],{"class":1012,"line":1114},[1010,2944,1117],{"class":1024},[1010,2946,1120],{"class":1020},[1010,2948,2949,2951,2953,2955,2957],{"class":1012,"line":1123},[1010,2950,1126],{"class":1024},[1010,2952,933],{"class":1020},[1010,2954,1131],{"class":1024},[1010,2956,1134],{"class":1061},[1010,2958,1120],{"class":1020},[1010,2960,2961,2963],{"class":1012,"line":1139},[1010,2962,1142],{"class":1024},[1010,2964,1120],{"class":1020},[1010,2966,2967],{"class":1012,"line":1147},[1010,2968,1150],{"class":1020},[1010,2970,2971,2973,2975,2977,2979,2981],{"class":1012,"line":1153},[1010,2972,1156],{"class":1024},[1010,2974,1085],{"class":1084},[1010,2976,1034],{"class":1033},[1010,2978,1163],{"class":1037},[1010,2980,1166],{"class":1033},[1010,2982,1120],{"class":1020},[1010,2984,2985,2987,2989,2991,2993,2995],{"class":1012,"line":1171},[1010,2986,1174],{"class":1024},[1010,2988,1085],{"class":1084},[1010,2990,1034],{"class":1033},[1010,2992,1181],{"class":1037},[1010,2994,1166],{"class":1033},[1010,2996,1120],{"class":1020},[1010,2998,2999,3001,3003,3005,3007,3009],{"class":1012,"line":1188},[1010,3000,1191],{"class":1024},[1010,3002,1085],{"class":1084},[1010,3004,1034],{"class":1033},[1010,3006,1198],{"class":1037},[1010,3008,1166],{"class":1033},[1010,3010,1120],{"class":1020},[1010,3012,3013],{"class":1012,"line":1205},[1010,3014,1208],{"class":1020},[1010,3016,3017],{"class":1012,"line":1211},[1010,3018,1214],{"class":1020},[1010,3020,3021],{"class":1012,"line":1217},[1010,3022,1047],{"emptyLinePlaceholder":8},[1010,3024,3025,3027,3029,3031,3033,3035,3037,3039],{"class":1012,"line":1222},[1010,3026,1225],{"class":1016},[1010,3028,1228],{"class":1020},[1010,3030,1231],{"class":1024},[1010,3032,1234],{"class":1061},[1010,3034,1034],{"class":1033},[1010,3036,1239],{"class":1037},[1010,3038,1166],{"class":1033},[1010,3040,1244],{"class":1020},[1010,3042,3043,3045,3047,3049,3051,3053,3055,3057,3059,3061,3063,3065,3067,3069],{"class":1012,"line":1247},[1010,3044,1142],{"class":1024},[1010,3046,933],{"class":1020},[1010,3048,1254],{"class":1068},[1010,3050,1257],{"class":1020},[1010,3052,1261],{"class":1260},[1010,3054,1264],{"class":1020},[1010,3056,5],{"class":1068},[1010,3058,1269],{"class":1020},[1010,3060,1272],{"class":1024},[1010,3062,1085],{"class":1084},[1010,3064,1034],{"class":1033},[1010,3066,1279],{"class":1037},[1010,3068,1166],{"class":1033},[1010,3070,1284],{"class":1020},[1010,3072,3073],{"class":1012,"line":1287},[1010,3074,1290],{"class":1016},[1010,3076,3077],{"class":1012,"line":1293},[1010,3078,1296],{"class":1020},[856,3080,1299],{},[1301,3082,3083,3091],{},[1304,3084,3085],{},[1307,3086,3087,3089],{},[1310,3088,1312],{},[1310,3090,1315],{},[1317,3092,3093,3101,3109],{},[1307,3094,3095,3099],{},[1322,3096,3097],{},[888,3098,1326],{},[1322,3100,1329],{},[1307,3102,3103,3107],{},[1322,3104,3105],{},[888,3106,1336],{},[1322,3108,1339],{},[1307,3110,3111,3115],{},[1322,3112,3113],{},[888,3114,1346],{},[1322,3116,1349],{},[856,3118,1352,3119,1356,3121,1096,3123,1363,3125,1367,3127,1371],{},[888,3120,1355],{},[888,3122,1359],{},[888,3124,1362],{},[888,3126,1366],{},[888,3128,1370],{},[1373,3130,3131],{},[856,3132,3133,1379,3135,1383,3137,1387,3139,1390],{},[888,3134,898],{},[888,3136,1382],{},[888,3138,1386],{},[888,3140,1346],{},[877,3142],{},[880,3144,937],{"id":1395},[856,3146,3147,1400,3149,1404],{},[888,3148,943],{},[888,3150,1403],{},[1001,3152,3153],{"className":1003,"code":1407,"language":1005,"meta":1006,"style":1006},[888,3154,3155,3173,3177,3207,3211,3225,3229,3233],{"__ignoreMap":1006},[1010,3156,3157,3159,3161,3163,3165,3167,3169,3171],{"class":1012,"line":1013},[1010,3158,1017],{"class":1016},[1010,3160,1021],{"class":1020},[1010,3162,943],{"class":1024},[1010,3164,1027],{"class":1020},[1010,3166,1030],{"class":1016},[1010,3168,1034],{"class":1033},[1010,3170,1038],{"class":1037},[1010,3172,1041],{"class":1033},[1010,3174,3175],{"class":1012,"line":1044},[1010,3176,1047],{"emptyLinePlaceholder":8},[1010,3178,3179,3181,3183,3185,3187,3189,3191,3193,3195,3197,3199,3201,3203,3205],{"class":1012,"line":1050},[1010,3180,1054],{"class":1053},[1010,3182,1058],{"class":1057},[1010,3184,1062],{"class":1061},[1010,3186,1065],{"class":1016},[1010,3188,1444],{"class":1068},[1010,3190,1257],{"class":1020},[1010,3192,1449],{"class":1024},[1010,3194,1096],{"class":1020},[1010,3196,1454],{"class":1024},[1010,3198,1096],{"class":1020},[1010,3200,1081],{"class":1024},[1010,3202,1096],{"class":1020},[1010,3204,1463],{"class":1024},[1010,3206,1214],{"class":1020},[1010,3208,3209],{"class":1012,"line":1075},[1010,3210,1047],{"emptyLinePlaceholder":8},[1010,3212,3213,3215,3217,3219,3221,3223],{"class":1012,"line":1114},[1010,3214,1225],{"class":1016},[1010,3216,1228],{"class":1020},[1010,3218,1231],{"class":1024},[1010,3220,933],{"class":1020},[1010,3222,1482],{"class":1024},[1010,3224,1244],{"class":1020},[1010,3226,3227],{"class":1012,"line":1123},[1010,3228,1490],{"class":1489},[1010,3230,3231],{"class":1012,"line":1139},[1010,3232,1495],{"class":1489},[1010,3234,3235],{"class":1012,"line":1147},[1010,3236,1296],{"class":1020},[856,3238,1502],{},[901,3240,3241,3243,3251,3253,3255,3257],{"level":903},[905,3242,1508],{"id":1507},[856,3244,1511,3245,1514,3247,1518,3249,1522],{},[888,3246,951],{},[888,3248,1517],{},[888,3250,1521],{},[905,3252,1526],{"id":1525},[856,3254,1529],{},[905,3256,1533],{"id":1532},[856,3258,1536,3259,1540,3261,933],{},[888,3260,1539],{},[888,3262,1543],{},[995,3264,1547,3265,1550],{"id":1546},[888,3266,951],{},[856,3268,1553,3269,1556,3271,1096,3273,1096,3275,1363,3277,1568],{},[888,3270,951],{},[888,3272,1559],{},[888,3274,1562],{},[888,3276,1463],{},[888,3278,1567],{},[1301,3280,3281,3289],{},[1304,3282,3283],{},[1307,3284,3285,3287],{},[1310,3286,1577],{},[1310,3288,1580],{},[1317,3290,3291,3303,3315,3323,3331,3339],{},[1307,3292,3293,3297],{},[1322,3294,3295],{},[888,3296,1559],{},[1322,3298,1591,3299,1518,3301,1598],{},[888,3300,1594],{},[888,3302,1597],{},[1307,3304,3305,3309],{},[1322,3306,3307],{},[888,3308,1567],{},[1322,3310,1607,3311,1518,3313,1613],{},[888,3312,1610],{},[888,3314,1597],{},[1307,3316,3317,3321],{},[1322,3318,3319],{},[888,3320,1463],{},[1322,3322,1622],{},[1307,3324,3325,3329],{},[1322,3326,3327],{},[888,3328,1562],{},[1322,3330,1631],{},[1307,3332,3333,3337],{},[1322,3334,3335],{},[888,3336,1638],{},[1322,3338,1641],{},[1307,3340,3341,3345],{},[1322,3342,3343],{},[888,3344,1539],{},[1322,3346,1650,3347,1653],{},[888,3348,1543],{},[1373,3350,3351],{},[856,3352,1547,3353,1660,3355,1663],{},[888,3354,1567],{},[888,3356,1597],{},[877,3358],{},[880,3360,1669],{"id":1668},[856,3362,3363,1674],{},[888,3364,988],{},[995,3366,1678],{"id":1677},[856,3368,1681],{},[1301,3370,3371,3381],{},[1304,3372,3373],{},[1307,3374,3375,3377,3379],{},[1310,3376,1690],{},[1310,3378,1693],{},[1310,3380,1580],{},[1317,3382,3383,3395,3407],{},[1307,3384,3385,3389,3393],{},[1322,3386,3387],{},[888,3388,1704],{},[1322,3390,3391],{},[888,3392,1709],{},[1322,3394,1712],{},[1307,3396,3397,3401,3405],{},[1322,3398,3399],{},[888,3400,1719],{},[1322,3402,3403],{},[888,3404,1724],{},[1322,3406,1727],{},[1307,3408,3409,3413,3417],{},[1322,3410,3411],{},[888,3412,1734],{},[1322,3414,3415],{},[888,3416,1739],{},[1322,3418,1742],{},[1301,3420,3421,3433],{},[1304,3422,3423],{},[1307,3424,3425,3427,3429,3431],{},[1310,3426,1751],{},[1310,3428,1693],{},[1310,3430,1756],{},[1310,3432,1580],{},[1317,3434,3435,3449,3463],{},[1307,3436,3437,3441,3445,3447],{},[1322,3438,3439],{},[888,3440,1767],{},[1322,3442,3443],{},[888,3444,1724],{},[1322,3446,1774],{},[1322,3448,1777],{},[1307,3450,3451,3455,3459,3461],{},[1322,3452,3453],{},[888,3454,1784],{},[1322,3456,3457],{},[888,3458,1789],{},[1322,3460,1774],{},[1322,3462,1794],{},[1307,3464,3465,3469,3473,3475],{},[1322,3466,3467],{},[888,3468,1801],{},[1322,3470,3471],{},[888,3472,1739],{},[1322,3474,1808],{},[1322,3476,1811],{},[995,3478,1815],{"id":1814},[901,3480,3481,3483,3485,3487,3497,3499,3505,3507,3513,3515,3519,3521,3531,3533,3537,3539,3545,3547,3551,3553,3565,3567],{":level":903},[905,3482,1821],{"id":1820},[856,3484,1824],{},[905,3486,1828],{"id":1827},[856,3488,1831,3489,1834,3491,1837,3493,1841,3495,1845],{},[888,3490,951],{},[888,3492,1638],{},[888,3494,1840],{},[888,3496,1844],{},[905,3498,1849],{"id":1848},[856,3500,1852,3501,1856,3503,1860],{},[888,3502,1855],{},[888,3504,1859],{},[905,3506,1864],{"id":1863},[856,3508,1867,3509,1870,3511,1873],{},[888,3510,1719],{},[888,3512,1734],{},[905,3514,1877],{"id":1876},[856,3516,1880,3517,1884],{},[888,3518,1883],{},[905,3520,1888],{"id":1887},[856,3522,1891,3523,1895,3525,1899,3527,1903,3529,1907],{},[888,3524,1894],{},[888,3526,1898],{},[888,3528,1902],{},[888,3530,1906],{},[905,3532,1911],{"id":1910},[856,3534,940,3535,1917],{},[888,3536,1916],{},[905,3538,1921],{"id":1920},[856,3540,1924,3541,1928,3543,1931],{},[888,3542,1927],{},[888,3544,1326],{},[905,3546,1935],{"id":1934},[856,3548,1938,3549,1942],{},[888,3550,1941],{},[905,3552,1946],{"id":1945},[856,3554,1949,3555,1903,3557,1956,3559,1960,3561,1928,3563,933],{},[888,3556,1952],{},[888,3558,1955],{},[888,3560,1959],{},[888,3562,1963],{},[888,3564,1326],{},[905,3566,1969],{"id":1968},[856,3568,1972],{},[1373,3570,3571],{},[856,3572,1977],{},[877,3574],{},[880,3576,1983],{"id":1982},[856,3578,1986],{},[995,3580,1990],{"id":1989},[856,3582,1993],{},[1301,3584,3585,3593],{},[1304,3586,3587],{},[1307,3588,3589,3591],{},[1310,3590,2002],{},[1310,3592,2005],{},[1317,3594,3595,3603,3611,3619],{},[1307,3596,3597,3601],{},[1322,3598,3599],{},[888,3600,928],{},[1322,3602,2016],{},[1307,3604,3605,3609],{},[1322,3606,3607],{},[888,3608,2023],{},[1322,3610,2026],{},[1307,3612,3613,3617],{},[1322,3614,3615],{},[888,3616,2033],{},[1322,3618,2036],{},[1307,3620,3621,3625],{},[1322,3622,3623],{},[888,3624,2043],{},[1322,3626,2046],{},[995,3628,2050],{"id":2049},[856,3630,3631,2056,3633,2060,3635,2064],{},[888,3632,2055],{},[888,3634,2059],{},[888,3636,2063],{},[856,3638,1547,3639,2070,3641,2074],{},[888,3640,2069],{},[888,3642,2073],{},[1001,3644,3645],{"className":2077,"code":2078,"language":2079,"meta":1006,"style":1006},[888,3646,3647,3653,3661],{"__ignoreMap":1006},[1010,3648,3649,3651],{"class":1012,"line":1013},[1010,3650,2086],{"class":1016},[1010,3652,2089],{"class":1020},[1010,3654,3655,3657,3659],{"class":1012,"line":1044},[1010,3656,2095],{"class":2094},[1010,3658,1085],{"class":1053},[1010,3660,2100],{"class":1037},[1010,3662,3663,3665,3667],{"class":1012,"line":1050},[1010,3664,2105],{"class":2094},[1010,3666,1085],{"class":1053},[1010,3668,2110],{"class":1037},[856,3670,2113,3671,2117],{},[888,3672,2116],{},[2119,3674,3675,3679,3683,3691,3695,3699],{},[2122,3676,2124,3677,2128],{},[888,3678,2127],{},[2122,3680,2131,3681,2134],{},[888,3682,2073],{},[2122,3684,2137,3685,2141,3687,2145,3689],{},[888,3686,2140],{},[888,3688,2144],{},[888,3690,2148],{},[2122,3692,2151,3693],{},[888,3694,2154],{},[2122,3696,2157,3697,2160],{},[888,3698,943],{},[2122,3700,2163,3701],{},[888,3702,962],{},[856,3704,2168],{},[995,3706,2172],{"id":2171},[856,3708,2175],{},[1301,3710,3711,3721],{},[1304,3712,3713],{},[1307,3714,3715,3717,3719],{},[1310,3716,2184],{},[1310,3718,2187],{},[1310,3720,1580],{},[1317,3722,3723,3735],{},[1307,3724,3725,3729,3731],{},[1322,3726,3727],{},[888,3728,2198],{},[1322,3730,2201],{},[1322,3732,2204,3733,2208],{},[888,3734,2207],{},[1307,3736,3737,3741,3743],{},[1322,3738,3739],{},[888,3740,2198],{},[1322,3742,2086],{},[1322,3744,2219,3745,2223,3747,1518,3749],{},[888,3746,2222],{},[888,3748,988],{},[888,3750,2228],{},[856,3752,2231,3753,2234],{},[888,3754,988],{},[995,3756,2238],{"id":2237},[856,3758,2241],{},[1001,3760,3761],{"className":1003,"code":2244,"language":1005,"meta":1006,"style":1006},[888,3762,3763,3769,3775,3781,3787],{"__ignoreMap":1006},[1010,3764,3765,3767],{"class":1012,"line":1013},[1010,3766,1017],{"class":1016},[1010,3768,2253],{"class":1020},[1010,3770,3771,3773],{"class":1012,"line":1044},[1010,3772,2258],{"class":1024},[1010,3774,1120],{"class":1020},[1010,3776,3777,3779],{"class":1012,"line":1050},[1010,3778,2265],{"class":1024},[1010,3780,1120],{"class":1020},[1010,3782,3783,3785],{"class":1012,"line":1075},[1010,3784,2272],{"class":1024},[1010,3786,1120],{"class":1020},[1010,3788,3789,3791,3793,3795,3797],{"class":1012,"line":1114},[1010,3790,2279],{"class":1020},[1010,3792,1030],{"class":1016},[1010,3794,1034],{"class":1033},[1010,3796,1038],{"class":1037},[1010,3798,1041],{"class":1033},[2289,3800,3801],{},[856,3802,2293,3803,2296,3805,2299],{},[888,3804,951],{},[888,3806,988],{},[877,3808],{},[880,3810,2305],{"id":2304},[856,3812,2308],{},[901,3814,3815,3817,3821,3823,3825,3907,3909,3913,3915,3919,3929,3931],{"level":903},[905,3816,2314],{"id":2313},[856,3818,2317,3819,2321],{},[888,3820,2320],{},[905,3822,2325],{"id":2324},[856,3824,2328],{},[1001,3826,3827],{"className":1003,"code":2331,"language":1005,"meta":1006,"style":1006},[888,3828,3829,3833,3851,3869,3887,3903],{"__ignoreMap":1006},[1010,3830,3831],{"class":1012,"line":1013},[1010,3832,2338],{"class":1020},[1010,3834,3835,3837,3839,3841,3843,3845,3847,3849],{"class":1012,"line":1044},[1010,3836,2343],{"class":1033},[1010,3838,2346],{"class":1037},[1010,3840,2349],{"class":1033},[1010,3842,2352],{"class":1020},[1010,3844,2349],{"class":1033},[1010,3846,2357],{"class":1037},[1010,3848,2349],{"class":1033},[1010,3850,1120],{"class":1020},[1010,3852,3853,3855,3857,3859,3861,3863,3865,3867],{"class":1012,"line":1050},[1010,3854,2343],{"class":1033},[1010,3856,2368],{"class":1037},[1010,3858,2349],{"class":1033},[1010,3860,2352],{"class":1020},[1010,3862,2349],{"class":1033},[1010,3864,2377],{"class":1037},[1010,3866,2349],{"class":1033},[1010,3868,1120],{"class":1020},[1010,3870,3871,3873,3875,3877,3879,3881,3883,3885],{"class":1012,"line":1075},[1010,3872,2343],{"class":1033},[1010,3874,2388],{"class":1037},[1010,3876,2349],{"class":1033},[1010,3878,2352],{"class":1020},[1010,3880,2349],{"class":1033},[1010,3882,2397],{"class":1037},[1010,3884,2349],{"class":1033},[1010,3886,1120],{"class":1020},[1010,3888,3889,3891,3893,3895,3897,3899,3901],{"class":1012,"line":1114},[1010,3890,2343],{"class":1033},[1010,3892,888],{"class":1037},[1010,3894,2349],{"class":1033},[1010,3896,2352],{"class":1020},[1010,3898,2349],{"class":1033},[1010,3900,2416],{"class":1037},[1010,3902,2419],{"class":1033},[1010,3904,3905],{"class":1012,"line":1123},[1010,3906,1296],{"class":1020},[905,3908,2427],{"id":2426},[856,3910,2430,3911,2434],{},[888,3912,2433],{},[905,3914,2438],{"id":2437},[856,3916,2441,3917,2444],{},[888,3918,988],{},[2446,3920,3921,3925],{},[2122,3922,3923,2453],{},[888,3924,2452],{},[2122,3926,3927,2458],{},[888,3928,1883],{},[905,3930,2462],{"id":2461},[856,3932,2465,3933,2469,3935,2473],{},[860,3934,2468],{},[888,3936,2472],{},[1373,3938,3939],{},[856,3940,2478],{},[877,3942],{},[880,3944,2484],{"id":2483},[856,3946,2487,3947,2490,3949,2493,3951,2497],{},[888,3948,992],{},[888,3950,1898],{},[888,3952,2496],{},[856,3954,2500,3955,1903,3957,2505],{},[888,3956,1902],{},[888,3958,1906],{},[877,3960],{},[880,3962,2511],{"id":2510},[1301,3964,3965,3975],{},[1304,3966,3967],{},[1307,3968,3969,3971,3973],{},[1310,3970,2184],{},[1310,3972,2187],{},[1310,3974,1580],{},[1317,3976,3977,3987,3997,4007,4017,4027,4037,4047,4057],{},[1307,3978,3979,3983,3985],{},[1322,3980,3981],{},[888,3982,2532],{},[1322,3984,2201],{},[1322,3986,2537],{},[1307,3988,3989,3993,3995],{},[1322,3990,3991],{},[888,3992,2532],{},[1322,3994,2086],{},[1322,3996,2548],{},[1307,3998,3999,4003,4005],{},[1322,4000,4001],{},[888,4002,2555],{},[1322,4004,2086],{},[1322,4006,2560],{},[1307,4008,4009,4013,4015],{},[1322,4010,4011],{},[888,4012,2198],{},[1322,4014,2201],{},[1322,4016,2571],{},[1307,4018,4019,4023,4025],{},[1322,4020,4021],{},[888,4022,2198],{},[1322,4024,2086],{},[1322,4026,2582],{},[1307,4028,4029,4033,4035],{},[1322,4030,4031],{},[888,4032,2589],{},[1322,4034,2086],{},[1322,4036,2594],{},[1307,4038,4039,4043,4045],{},[1322,4040,4041],{},[888,4042,2601],{},[1322,4044,2086],{},[1322,4046,2606],{},[1307,4048,4049,4053,4055],{},[1322,4050,4051],{},[888,4052,2613],{},[1322,4054,2201],{},[1322,4056,2618],{},[1307,4058,4059,4063,4065],{},[1322,4060,4061],{},[888,4062,2613],{},[1322,4064,2086],{},[1322,4066,2629],{},[877,4068],{},[880,4070,2635],{"id":2634},[995,4072,2639],{"id":2638},[2641,4074,4075,4085],{},[2644,4076,4077],{"name":2646,"type":2647,"default":2648},[856,4078,2651,4079,2655,4081,1895,4083,933],{},[888,4080,2654],{},[888,4082,2658],{},[888,4084,2661],{},[2644,4086,4087],{"name":2664,"type":2647,"default":2665},[856,4088,2668],{},[995,4090,2672],{"id":2671},[2641,4092,4093],{},[2644,4094,4095],{"name":2677,"type":2647,"default":2678},[856,4096,2681],{},[995,4098,2685],{"id":2684},[2641,4100,4101,4107,4113,4117,4125],{},[2644,4102,4103],{"name":2690,"type":2691},[856,4104,2694,4105,2697],{},[888,4106,1382],{},[2644,4108,4109],{"name":2700,"type":2691},[856,4110,2703,4111,2706],{},[888,4112,1386],{},[2644,4114,4115],{"name":2709,"type":2691},[856,4116,2712],{},[2644,4118,4119],{"name":1704,"type":2691},[856,4120,2717,4121,2720,4123,2724],{},[888,4122,1709],{},[888,4124,2723],{},[2644,4126,4127],{"name":2727,"type":2691},[856,4128,2730,4129,2734],{},[888,4130,2733],{},[995,4132,2738],{"id":2737},[856,4134,2741,4135,2745,4137,2750],{},[888,4136,2744],{},[865,4138,2749],{"href":2748},[2752,4140,2754],{},{"title":1006,"searchDepth":1044,"depth":1044,"links":4142},[4143,4146,4149,4153,4159,4160,4161,4162],{"id":882,"depth":1044,"text":883,"children":4144},[4145],{"id":997,"depth":1050,"text":898},{"id":1395,"depth":1044,"text":937,"children":4147},[4148],{"id":1546,"depth":1050,"text":2763},{"id":1668,"depth":1044,"text":1669,"children":4150},[4151,4152],{"id":1677,"depth":1050,"text":1678},{"id":1814,"depth":1050,"text":1815},{"id":1982,"depth":1044,"text":1983,"children":4154},[4155,4156,4157,4158],{"id":1989,"depth":1050,"text":1990},{"id":2049,"depth":1050,"text":2050},{"id":2171,"depth":1050,"text":2172},{"id":2237,"depth":1050,"text":2238},{"id":2304,"depth":1044,"text":2305},{"id":2483,"depth":1044,"text":2484},{"id":2510,"depth":1044,"text":2511},{"id":2634,"depth":1044,"text":2635,"children":4163},[4164,4165,4166,4167],{"id":2638,"depth":1050,"text":2639},{"id":2671,"depth":1050,"text":2672},{"id":2684,"depth":1050,"text":2685},{"id":2737,"depth":1050,"text":2738},{},{"title":123,"description":2783},1780436282235]