[{"data":1,"prerenderedAt":7054},["ShallowReactive",2],{"navLinks":3,"sidebar_docs_navigation_\u002Fdocs\u002Fiam":64,"navigation":257,"navLinks_footer":837,"\u002Fdocs\u002Fiam\u002Fessentials\u002Fanomalies_page":850,"\u002Fdocs\u002Fiam\u002Fessentials\u002Fanomalies_surround":4264,"\u002Fdocs\u002Fiam\u002Fessentials\u002Fanomalies":4267},{"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":95,"body":852,"description":4256,"extension":4257,"icon":4258,"meta":4259,"module":4260,"navigation":8,"path":96,"rawbody":4261,"seo":4262,"stem":97,"__hash__":4263},"docs\u002Fdocs\u002Fiam\u002F01.essentials\u002F03.anomalies.md",{"type":853,"value":854,"toc":4232},"minimark",[855,876,896,899,904,914,977,1002,1009,1011,1015,1270,1378,1381,1383,1387,1397,1403,1442,1473,1479,1482,1571,1574,1576,1580,1585,1597,1629,1636,1661,1664,1743,1815,1819,1831,1864,1889,1891,1983,1987,1994,2027,2037,2039,2126,2130,2137,2170,2181,2183,2267,2271,2274,2307,2309,2373,2377,2390,2423,2425,2522,2526,2545,2578,2599,2601,2696,2700,2727,2740,2832,2848,2871,2873,2969,3065,3069,3080,3096,3099,3185,3188,3221,3223,3318,3325,3328,3424,3426,3430,3443,3469,3475,3568,3593,3596,3601,3690,3692,3696,3708,3744,3747,3762,3764,3767,3778,3797,3800,3806,3808,3812,3887,3901,3912,3914,3918,4083,4085,4089,4092,4220,4228],[856,857,858,859,863,864,867,868,871,872,875],"p",{},"Every time a refresh token is used, the IAM service runs ",[860,861,862],"code",{},"strangeThings()"," to decide whether the request is legitimate, suspicious, or hostile. The function queries the ",[860,865,866],{},"refresh_tokens",", ",[860,869,870],{},"users",", and ",[860,873,874],{},"visitors"," tables in a single lookup, then runs nine sequential checks against the results. The first check that fails short-circuits the rest and returns immediately.",[856,877,878,879,882,883,885,886,889,890,885,892,895],{},"Each failure carries a ",[860,880,881],{},"reqMFA"," flag. When ",[860,884,881],{}," is ",[860,887,888],{},"true",", the caller sends an email OTP challenge and the user can recover by proving their identity. When ",[860,891,881],{},[860,893,894],{},"false",", the request is a hard block: the token is revoked and the user must log in again.",[897,898],"hr",{},[900,901,903],"h2",{"id":902},"routes","Routes",[856,905,906,909,910,913],{},[860,907,908],{},"strangeThings"," is called from two places in the middleware chain, each with a different value for the ",[860,911,912],{},"rotated"," parameter.",[915,916,917,937],"table",{},[918,919,920],"thead",{},[921,922,923,927,930,934],"tr",{},[924,925,926],"th",{},"Caller",[924,928,929],{},"Route",[924,931,932],{},[860,933,912],{},[924,935,936],{},"Purpose",[938,939,940,958],"tbody",{},[921,941,942,948,951,955],{},[943,944,945],"td",{},[860,946,947],{},"protectRoute",[943,949,950],{},"Every authenticated request",[943,952,953],{},[860,954,894],{},[943,956,957],{},"Routine anomaly check during access-token verification",[921,959,960,965,970,974],{},[943,961,962],{},[860,963,964],{},"rotateCredentials",[943,966,967],{},[860,968,969],{},"POST \u002Fauth\u002Fuser\u002Frefresh-session",[943,971,972],{},[860,973,888],{},[943,975,976],{},"Rotation-time check with reuse detection enabled",[856,978,979,980,885,982,984,985,988,989,885,991,993,994,997,998,1001],{},"When ",[860,981,912],{},[860,983,888],{},", the function adds an extra condition: if the token has already been consumed (",[860,986,987],{},"usage_count > 0","), the request is hard-blocked. This catches replay attacks during the rotation window. When ",[860,990,912],{},[860,992,894],{},", the function skips the ",[860,995,996],{},"usage_count"," check because ",[860,999,1000],{},"consumeAndVerifyRefreshToken"," handles reuse detection separately downstream.",[856,1003,1004,1005,1008],{},"See ",[1006,1007,91],"a",{"href":92}," for the full rotation and consumption flow.",[897,1010],{},[900,1012,1014],{"id":1013},"function-signature","Function signature",[1016,1017,1022],"pre",{"className":1018,"code":1019,"language":1020,"meta":1021,"style":1021},"language-ts shiki shiki-themes light-plus light-plus dracula","import { strangeThings } from '@riavzon\u002Fauth'\n\nconst { valid, reason, reqMFA, userId, visitorId } = await strangeThings(\n  rawRefreshToken,          \u002F\u002F Raw token string from the client cookie\n  canary_id,                \u002F\u002F canary_id cookie value\n  req.ip!,                  \u002F\u002F Client IP address\n  req.get('User-Agent')!,   \u002F\u002F Raw User-Agent header\n  false                     \u002F\u002F true when called from the rotation controller\n)\n\nif (!valid && reqMFA) {\n  \u002F\u002F Issue adaptive MFA email — see MFA docs\n} else if (!valid) {\n  \u002F\u002F Hard block — revoke session and return 401\n}\n","ts","",[860,1023,1024,1057,1063,1111,1124,1136,1157,1189,1199,1205,1210,1232,1238,1258,1264],{"__ignoreMap":1021},[1025,1026,1029,1033,1037,1040,1043,1046,1050,1054],"span",{"class":1027,"line":1028},"line",1,[1025,1030,1032],{"class":1031},"sZ328","import",[1025,1034,1036],{"class":1035},"sDd4n"," { ",[1025,1038,908],{"class":1039},"sjsA6",[1025,1041,1042],{"class":1035}," } ",[1025,1044,1045],{"class":1031},"from",[1025,1047,1049],{"class":1048},"sFkSl"," '",[1025,1051,1053],{"class":1052},"sFB1V","@riavzon\u002Fauth",[1025,1055,1056],{"class":1048},"'\n",[1025,1058,1060],{"class":1027,"line":1059},2,[1025,1061,1062],{"emptyLinePlaceholder":8},"\n",[1025,1064,1066,1070,1072,1076,1078,1081,1083,1085,1087,1090,1092,1095,1097,1101,1104,1108],{"class":1027,"line":1065},3,[1025,1067,1069],{"class":1068},"sl46w","const",[1025,1071,1036],{"class":1035},[1025,1073,1075],{"class":1074},"s3JHE","valid",[1025,1077,867],{"class":1035},[1025,1079,1080],{"class":1074},"reason",[1025,1082,867],{"class":1035},[1025,1084,881],{"class":1074},[1025,1086,867],{"class":1035},[1025,1088,1089],{"class":1074},"userId",[1025,1091,867],{"class":1035},[1025,1093,1094],{"class":1074},"visitorId",[1025,1096,1042],{"class":1035},[1025,1098,1100],{"class":1099},"saOXh","=",[1025,1102,1103],{"class":1031}," await",[1025,1105,1107],{"class":1106},"sHOzp"," strangeThings",[1025,1109,1110],{"class":1035},"(\n",[1025,1112,1114,1117,1120],{"class":1027,"line":1113},4,[1025,1115,1116],{"class":1039},"  rawRefreshToken",[1025,1118,1119],{"class":1035},",          ",[1025,1121,1123],{"class":1122},"sghk6","\u002F\u002F Raw token string from the client cookie\n",[1025,1125,1127,1130,1133],{"class":1027,"line":1126},5,[1025,1128,1129],{"class":1039},"  canary_id",[1025,1131,1132],{"class":1035},",                ",[1025,1134,1135],{"class":1122},"\u002F\u002F canary_id cookie value\n",[1025,1137,1139,1142,1145,1148,1151,1154],{"class":1027,"line":1138},6,[1025,1140,1141],{"class":1039},"  req",[1025,1143,1144],{"class":1035},".",[1025,1146,1147],{"class":1039},"ip",[1025,1149,1150],{"class":1099},"!",[1025,1152,1153],{"class":1035},",                  ",[1025,1155,1156],{"class":1122},"\u002F\u002F Client IP address\n",[1025,1158,1160,1162,1164,1167,1170,1173,1176,1178,1181,1183,1186],{"class":1027,"line":1159},7,[1025,1161,1141],{"class":1039},[1025,1163,1144],{"class":1035},[1025,1165,1166],{"class":1106},"get",[1025,1168,1169],{"class":1035},"(",[1025,1171,1172],{"class":1048},"'",[1025,1174,1175],{"class":1052},"User-Agent",[1025,1177,1172],{"class":1048},[1025,1179,1180],{"class":1035},")",[1025,1182,1150],{"class":1099},[1025,1184,1185],{"class":1035},",   ",[1025,1187,1188],{"class":1122},"\u002F\u002F Raw User-Agent header\n",[1025,1190,1192,1196],{"class":1027,"line":1191},8,[1025,1193,1195],{"class":1194},"sjR7W","  false",[1025,1197,1198],{"class":1122},"                     \u002F\u002F true when called from the rotation controller\n",[1025,1200,1202],{"class":1027,"line":1201},9,[1025,1203,1204],{"class":1035},")\n",[1025,1206,1208],{"class":1027,"line":1207},10,[1025,1209,1062],{"emptyLinePlaceholder":8},[1025,1211,1213,1216,1219,1221,1223,1226,1229],{"class":1027,"line":1212},11,[1025,1214,1215],{"class":1031},"if",[1025,1217,1218],{"class":1035}," (",[1025,1220,1150],{"class":1099},[1025,1222,1075],{"class":1039},[1025,1224,1225],{"class":1099}," &&",[1025,1227,1228],{"class":1039}," reqMFA",[1025,1230,1231],{"class":1035},") {\n",[1025,1233,1235],{"class":1027,"line":1234},12,[1025,1236,1237],{"class":1122},"  \u002F\u002F Issue adaptive MFA email — see MFA docs\n",[1025,1239,1241,1244,1247,1250,1252,1254,1256],{"class":1027,"line":1240},13,[1025,1242,1243],{"class":1035},"} ",[1025,1245,1246],{"class":1031},"else",[1025,1248,1249],{"class":1031}," if",[1025,1251,1218],{"class":1035},[1025,1253,1150],{"class":1099},[1025,1255,1075],{"class":1039},[1025,1257,1231],{"class":1035},[1025,1259,1261],{"class":1027,"line":1260},14,[1025,1262,1263],{"class":1122},"  \u002F\u002F Hard block — revoke session and return 401\n",[1025,1265,1267],{"class":1027,"line":1266},15,[1025,1268,1269],{"class":1035},"}\n",[915,1271,1272,1285],{},[918,1273,1274],{},[921,1275,1276,1279,1282],{},[924,1277,1278],{},"Parameter",[924,1280,1281],{},"Type",[924,1283,1284],{},"Description",[938,1286,1287,1306,1328,1345,1362],{},[921,1288,1289,1294,1299],{},[943,1290,1291],{},[860,1292,1293],{},"token",[943,1295,1296],{},[860,1297,1298],{},"string",[943,1300,1301,1302,1305],{},"The raw (unhashed) refresh token from the ",[860,1303,1304],{},"session"," cookie",[921,1307,1308,1313,1317],{},[943,1309,1310],{},[860,1311,1312],{},"cookie",[943,1314,1315],{},[860,1316,1298],{},[943,1318,1319,1320,1323,1324],{},"The ",[860,1321,1322],{},"canary_id"," cookie value. See ",[1006,1325,1327],{"href":1326},"#canary-id-lifecycle","canary_id lifecycle",[921,1329,1330,1335,1339],{},[943,1331,1332],{},[860,1333,1334],{},"ipAddress",[943,1336,1337],{},[860,1338,1298],{},[943,1340,1341,1342],{},"Client IP from ",[860,1343,1344],{},"req.ip",[921,1346,1347,1352,1356],{},[943,1348,1349],{},[860,1350,1351],{},"ua",[943,1353,1354],{},[860,1355,1298],{},[943,1357,1358,1359,1361],{},"Raw ",[860,1360,1175],{}," header string",[921,1363,1364,1368,1373],{},[943,1365,1366],{},[860,1367,912],{},[943,1369,1370],{},[860,1371,1372],{},"boolean",[943,1374,1375,1377],{},[860,1376,888],{}," when the token has already been rotated this request",[856,1379,1380],{},"The function hashes the token with SHA-256 before querying the database. No plaintext token ever reaches the database layer.",[897,1382],{},[900,1384,1386],{"id":1385},"the-data-lookup","The data lookup",[856,1388,1389,1390,1392,1393,1396],{},"Before any checks run, ",[860,1391,908],{}," check the local LRU cache from ",[860,1394,1395],{},"anomaliesCache",". if the cache exists and it marked as unresolved anomaly it short circuit and returns the cache results from the previous run.",[856,1398,1399,1400,1402],{},"When no cache exists, or it marked as resolved, ",[860,1401,908],{}," joins three tables in a single query:",[1404,1405,1406,1417,1432],"ul",{},[1407,1408,1409,1414,1415],"li",{},[1410,1411,1412],"strong",{},[860,1413,866],{}," provides the token's validity, expiry, creation time, and ",[860,1416,996],{},[1407,1418,1419,1423,1424,1427,1428,1431],{},[1410,1420,1421],{},[860,1422,870],{}," provides the ",[860,1425,1426],{},"visitor_id"," foreign key and the ",[860,1429,1430],{},"last_mfa_at"," timestamp",[1407,1433,1434,1438,1439,1180],{},[1410,1435,1436],{},[860,1437,874],{}," provides the stored fingerprint (IP, geo, User-Agent fields, device metadata, proxy\u002Fhosting flags, and ",[860,1440,1441],{},"suspicious_activity_score",[856,1443,1444,1445,1448,1449,1448,1452,1448,1455,1458,1459,1462,1463,1465,1466,1468,1469,1144],{},"The join key chain is: ",[860,1446,1447],{},"refresh_tokens.user_id"," to ",[860,1450,1451],{},"users.id",[860,1453,1454],{},"users.visitor_id",[860,1456,1457],{},"visitors.visitor_id",". If no row is found, the function returns ",[860,1460,1461],{},"{ valid: false, reqMFA: false }"," immediately. The ",[860,1464,874],{}," table is managed by the ",[1006,1467,399],{"href":35}," middleware and is described in ",[1006,1470,1472],{"href":1471},"#device-fingerprinting-and-the-bot-detector","Device Fingerprinting",[856,1474,1475,1476,1478],{},"The cache is a LRU instance available for library users via ",[860,1477,1395],{},", and its keyed by a hashed refresh token.",[856,1480,1481],{},"The cache body:",[1016,1483,1485],{"className":1018,"code":1484,"language":1020,"meta":1021,"style":1021},"interface AnomaliesCache {\n    anomalyType: string,\n    canaryCookie: string\n    visitorId?: string\n    userId?: number\n    resolved: boolean;\n    resolvable: boolean;\n}\n",[860,1486,1487,1499,1513,1523,1533,1543,1556,1567],{"__ignoreMap":1021},[1025,1488,1489,1492,1496],{"class":1027,"line":1028},[1025,1490,1491],{"class":1068},"interface",[1025,1493,1495],{"class":1494},"sFs1U"," AnomaliesCache",[1025,1497,1498],{"class":1035}," {\n",[1025,1500,1501,1504,1507,1510],{"class":1027,"line":1059},[1025,1502,1503],{"class":1039},"    anomalyType",[1025,1505,1506],{"class":1099},":",[1025,1508,1509],{"class":1494}," string",[1025,1511,1512],{"class":1035},",\n",[1025,1514,1515,1518,1520],{"class":1027,"line":1065},[1025,1516,1517],{"class":1039},"    canaryCookie",[1025,1519,1506],{"class":1099},[1025,1521,1522],{"class":1494}," string\n",[1025,1524,1525,1528,1531],{"class":1027,"line":1113},[1025,1526,1527],{"class":1039},"    visitorId",[1025,1529,1530],{"class":1099},"?:",[1025,1532,1522],{"class":1494},[1025,1534,1535,1538,1540],{"class":1027,"line":1126},[1025,1536,1537],{"class":1039},"    userId",[1025,1539,1530],{"class":1099},[1025,1541,1542],{"class":1494}," number\n",[1025,1544,1545,1548,1550,1553],{"class":1027,"line":1138},[1025,1546,1547],{"class":1039},"    resolved",[1025,1549,1506],{"class":1099},[1025,1551,1552],{"class":1494}," boolean",[1025,1554,1555],{"class":1035},";\n",[1025,1557,1558,1561,1563,1565],{"class":1027,"line":1159},[1025,1559,1560],{"class":1039},"    resolvable",[1025,1562,1506],{"class":1099},[1025,1564,1552],{"class":1494},[1025,1566,1555],{"class":1035},[1025,1568,1569],{"class":1027,"line":1191},[1025,1570,1269],{"class":1035},[856,1572,1573],{},"An entry is deleted when a user complete successfully a OTP challenge send to its email, or when the callers fails sending this email.",[897,1575],{},[900,1577,1579],{"id":1578},"the-nine-checks","The nine checks",[1581,1582,1584],"h3",{"id":1583},"_1-token-validity-and-reuse","1. Token validity and reuse",[856,1586,1587,1588,1591,1592,885,1594,1596],{},"The function inspects the token row from the database. If the token does not exist, has been revoked (",[860,1589,1590],{},"valid = false","), or has already been consumed while ",[860,1593,912],{},[860,1595,888],{},", the token is revoked and the request is blocked.",[915,1598,1599,1614],{},[918,1600,1601],{},[921,1602,1603,1607,1611],{},[924,1604,1605],{},[860,1606,1075],{},[924,1608,1609],{},[860,1610,881],{},[924,1612,1613],{},"Reason",[938,1615,1616],{},[921,1617,1618,1622,1626],{},[943,1619,1620],{},[860,1621,894],{},[943,1623,1624],{},[860,1625,894],{},[943,1627,1628],{},"Token not found, already invalid, or replayed",[856,1630,1631,1632,1635],{},"The revocation call ensures the token row is marked ",[860,1633,1634],{},"valid = 0"," even if the row was still technically valid but replayed. This prevents the same token from being retried.",[1637,1638,1639],"warning",{},[856,1640,1641,1642,1645,1646,1648,1649,1651,1652,1655,1656,1660],{},"When a replayed token is detected during rotation (",[860,1643,1644],{},"rotated = true"," and ",[860,1647,987],{},"), the service revokes the session. The separate ",[860,1650,1000],{}," path handles mass revocation (revoking ",[1410,1653,1654],{},"all"," sessions for the user) when a fully consumed token is presented a second time. Together, these two detection points make replay attacks observable from both the rotation controller and the consumption path. See ",[1006,1657,1659],{"href":1658},"\u002Fdocs\u002Fiam\u002Fessentials\u002Frefresh-tokens#reuse-detection","Reuse Detection"," for the full scenario walkthrough.",[856,1662,1663],{},"Cache:",[1016,1665,1667],{"className":1018,"code":1666,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n     anomalyType: 'No token found',\n     canaryCookie: cookie,\n     resolved: false,\n     resolvable: false,\n})\n",[860,1668,1669,1687,1704,1715,1727,1738],{"__ignoreMap":1021},[1025,1670,1671,1674,1676,1679,1681,1684],{"class":1027,"line":1028},[1025,1672,1673],{"class":1039},"cache",[1025,1675,1144],{"class":1035},[1025,1677,1678],{"class":1106},"set",[1025,1680,1169],{"class":1035},[1025,1682,1683],{"class":1039},"hashedClientToken",[1025,1685,1686],{"class":1035},", {\n",[1025,1688,1689,1692,1695,1697,1700,1702],{"class":1027,"line":1059},[1025,1690,1691],{"class":1039},"     anomalyType",[1025,1693,1506],{"class":1694},"s34zl",[1025,1696,1049],{"class":1048},[1025,1698,1699],{"class":1052},"No token found",[1025,1701,1172],{"class":1048},[1025,1703,1512],{"class":1035},[1025,1705,1706,1709,1711,1713],{"class":1027,"line":1065},[1025,1707,1708],{"class":1039},"     canaryCookie",[1025,1710,1506],{"class":1694},[1025,1712,1305],{"class":1039},[1025,1714,1512],{"class":1035},[1025,1716,1717,1720,1722,1725],{"class":1027,"line":1113},[1025,1718,1719],{"class":1039},"     resolved",[1025,1721,1506],{"class":1694},[1025,1723,1724],{"class":1194}," false",[1025,1726,1512],{"class":1035},[1025,1728,1729,1732,1734,1736],{"class":1027,"line":1126},[1025,1730,1731],{"class":1039},"     resolvable",[1025,1733,1506],{"class":1694},[1025,1735,1724],{"class":1194},[1025,1737,1512],{"class":1035},[1025,1739,1740],{"class":1027,"line":1138},[1025,1741,1742],{"class":1035},"})\n",[1016,1744,1746],{"className":1018,"code":1745,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n  anomalyType: 'token is invalid or being used more then ones',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: false,\n})\n",[860,1747,1748,1762,1778,1789,1800,1811],{"__ignoreMap":1021},[1025,1749,1750,1752,1754,1756,1758,1760],{"class":1027,"line":1028},[1025,1751,1673],{"class":1039},[1025,1753,1144],{"class":1035},[1025,1755,1678],{"class":1106},[1025,1757,1169],{"class":1035},[1025,1759,1683],{"class":1039},[1025,1761,1686],{"class":1035},[1025,1763,1764,1767,1769,1771,1774,1776],{"class":1027,"line":1059},[1025,1765,1766],{"class":1039},"  anomalyType",[1025,1768,1506],{"class":1694},[1025,1770,1049],{"class":1048},[1025,1772,1773],{"class":1052},"token is invalid or being used more then ones",[1025,1775,1172],{"class":1048},[1025,1777,1512],{"class":1035},[1025,1779,1780,1783,1785,1787],{"class":1027,"line":1065},[1025,1781,1782],{"class":1039},"  canaryCookie",[1025,1784,1506],{"class":1694},[1025,1786,1305],{"class":1039},[1025,1788,1512],{"class":1035},[1025,1790,1791,1794,1796,1798],{"class":1027,"line":1113},[1025,1792,1793],{"class":1039},"  resolved",[1025,1795,1506],{"class":1694},[1025,1797,1724],{"class":1194},[1025,1799,1512],{"class":1035},[1025,1801,1802,1805,1807,1809],{"class":1027,"line":1126},[1025,1803,1804],{"class":1039},"  resolvable",[1025,1806,1506],{"class":1694},[1025,1808,1724],{"class":1194},[1025,1810,1512],{"class":1035},[1025,1812,1813],{"class":1027,"line":1138},[1025,1814,1742],{"class":1035},[1581,1816,1818],{"id":1817},"_2-canary-cookie-mismatch","2. Canary cookie mismatch",[856,1820,1821,1822,1824,1825,1827,1828,1830],{},"The stored ",[860,1823,1322],{}," in the ",[860,1826,874],{}," table is compared against the ",[860,1829,1322],{}," cookie sent with the request. A mismatch means the request is arriving from a different browser or device than the one that established the session. MFA is required so the user can verify their identity from the new context.",[915,1832,1833,1847],{},[918,1834,1835],{},[921,1836,1837,1841,1845],{},[924,1838,1839],{},[860,1840,1075],{},[924,1842,1843],{},[860,1844,881],{},[924,1846,1613],{},[938,1848,1849],{},[921,1850,1851,1855,1859],{},[943,1852,1853],{},[860,1854,894],{},[943,1856,1857],{},[860,1858,888],{},[943,1860,1861],{},[860,1862,1863],{},"new device",[856,1865,1319,1866,1868,1869,867,1872,867,1875,1878,1879,1882,1883,1885,1886,1888],{},[860,1867,1322],{}," is a cryptographically random 64-character hex string set as an ",[860,1870,1871],{},"httpOnly",[860,1873,1874],{},"Secure",[860,1876,1877],{},"SameSite=Lax"," cookie with a 90-day TTL. It is generated by the ",[1006,1880,1881],{"href":404},"Bot Detector middleware"," on first contact and used as the primary key in the ",[860,1884,874],{}," table. See ",[1006,1887,1327],{"href":1326}," for how it flows through login and anomaly detection.",[856,1890,1663],{},[1016,1892,1894],{"className":1018,"code":1893,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n  anomalyType: 'new device',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: user_id,\n  visitorId: visitor_id,\n})\n",[860,1895,1896,1910,1924,1934,1944,1955,1967,1979],{"__ignoreMap":1021},[1025,1897,1898,1900,1902,1904,1906,1908],{"class":1027,"line":1028},[1025,1899,1673],{"class":1039},[1025,1901,1144],{"class":1035},[1025,1903,1678],{"class":1106},[1025,1905,1169],{"class":1035},[1025,1907,1683],{"class":1039},[1025,1909,1686],{"class":1035},[1025,1911,1912,1914,1916,1918,1920,1922],{"class":1027,"line":1059},[1025,1913,1766],{"class":1039},[1025,1915,1506],{"class":1694},[1025,1917,1049],{"class":1048},[1025,1919,1863],{"class":1052},[1025,1921,1172],{"class":1048},[1025,1923,1512],{"class":1035},[1025,1925,1926,1928,1930,1932],{"class":1027,"line":1065},[1025,1927,1782],{"class":1039},[1025,1929,1506],{"class":1694},[1025,1931,1305],{"class":1039},[1025,1933,1512],{"class":1035},[1025,1935,1936,1938,1940,1942],{"class":1027,"line":1113},[1025,1937,1793],{"class":1039},[1025,1939,1506],{"class":1694},[1025,1941,1724],{"class":1194},[1025,1943,1512],{"class":1035},[1025,1945,1946,1948,1950,1953],{"class":1027,"line":1126},[1025,1947,1804],{"class":1039},[1025,1949,1506],{"class":1694},[1025,1951,1952],{"class":1194}," true",[1025,1954,1512],{"class":1035},[1025,1956,1957,1960,1962,1965],{"class":1027,"line":1138},[1025,1958,1959],{"class":1039},"  userId",[1025,1961,1506],{"class":1694},[1025,1963,1964],{"class":1039}," user_id",[1025,1966,1512],{"class":1035},[1025,1968,1969,1972,1974,1977],{"class":1027,"line":1159},[1025,1970,1971],{"class":1039},"  visitorId",[1025,1973,1506],{"class":1694},[1025,1975,1976],{"class":1039}," visitor_id",[1025,1978,1512],{"class":1035},[1025,1980,1981],{"class":1027,"line":1191},[1025,1982,1742],{"class":1035},[1581,1984,1986],{"id":1985},"_3-idle-detection","3. Idle detection",[856,1988,1989,1990,1993],{},"If the visitor's ",[860,1991,1992],{},"last_seen"," timestamp is older than 24 hours, the session is flagged as idle. The assumption is that a legitimate user returning after a long gap should re-verify. MFA is triggered.",[915,1995,1996,2010],{},[918,1997,1998],{},[921,1999,2000,2004,2008],{},[924,2001,2002],{},[860,2003,1075],{},[924,2005,2006],{},[860,2007,881],{},[924,2009,1613],{},[938,2011,2012],{},[921,2013,2014,2018,2022],{},[943,2015,2016],{},[860,2017,894],{},[943,2019,2020],{},[860,2021,888],{},[943,2023,2024],{},[860,2025,2026],{},"idle",[856,2028,1319,2029,2031,2032,2034,2035,1144],{},[860,2030,1992],{}," column in the ",[860,2033,874],{}," table is updated automatically by the Bot Detector on every request. It reflects the last time any request (not just auth requests) was seen from that ",[860,2036,1322],{},[856,2038,1663],{},[1016,2040,2042],{"className":1018,"code":2041,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n  anomalyType: 'idle',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: user_id,\n  visitorId: visitor_id,\n})\n",[860,2043,2044,2058,2072,2082,2092,2102,2112,2122],{"__ignoreMap":1021},[1025,2045,2046,2048,2050,2052,2054,2056],{"class":1027,"line":1028},[1025,2047,1673],{"class":1039},[1025,2049,1144],{"class":1035},[1025,2051,1678],{"class":1106},[1025,2053,1169],{"class":1035},[1025,2055,1683],{"class":1039},[1025,2057,1686],{"class":1035},[1025,2059,2060,2062,2064,2066,2068,2070],{"class":1027,"line":1059},[1025,2061,1766],{"class":1039},[1025,2063,1506],{"class":1694},[1025,2065,1049],{"class":1048},[1025,2067,2026],{"class":1052},[1025,2069,1172],{"class":1048},[1025,2071,1512],{"class":1035},[1025,2073,2074,2076,2078,2080],{"class":1027,"line":1065},[1025,2075,1782],{"class":1039},[1025,2077,1506],{"class":1694},[1025,2079,1305],{"class":1039},[1025,2081,1512],{"class":1035},[1025,2083,2084,2086,2088,2090],{"class":1027,"line":1113},[1025,2085,1793],{"class":1039},[1025,2087,1506],{"class":1694},[1025,2089,1724],{"class":1194},[1025,2091,1512],{"class":1035},[1025,2093,2094,2096,2098,2100],{"class":1027,"line":1126},[1025,2095,1804],{"class":1039},[1025,2097,1506],{"class":1694},[1025,2099,1952],{"class":1194},[1025,2101,1512],{"class":1035},[1025,2103,2104,2106,2108,2110],{"class":1027,"line":1138},[1025,2105,1959],{"class":1039},[1025,2107,1506],{"class":1694},[1025,2109,1964],{"class":1039},[1025,2111,1512],{"class":1035},[1025,2113,2114,2116,2118,2120],{"class":1027,"line":1159},[1025,2115,1971],{"class":1039},[1025,2117,1506],{"class":1694},[1025,2119,1976],{"class":1039},[1025,2121,1512],{"class":1035},[1025,2123,2124],{"class":1027,"line":1191},[1025,2125,1742],{"class":1035},[1581,2127,2129],{"id":2128},"_4-session-count-limit","4. Session count limit",[856,2131,2132,2133,2136],{},"The function counts all valid (non-expired, non-revoked) refresh tokens for the user. If the count equals or exceeds ",[860,2134,2135],{},"maxAllowedSessionsPerUser",", MFA is required. This prevents an attacker from silently opening many sessions.",[915,2138,2139,2153],{},[918,2140,2141],{},[921,2142,2143,2147,2151],{},[924,2144,2145],{},[860,2146,1075],{},[924,2148,2149],{},[860,2150,881],{},[924,2152,1613],{},[938,2154,2155],{},[921,2156,2157,2161,2165],{},[943,2158,2159],{},[860,2160,894],{},[943,2162,2163],{},[860,2164,888],{},[943,2166,2167],{},[860,2168,2169],{},"more than N active sessions",[856,2171,2172,2173,2176,2177,1144],{},"This check respects the ",[860,2174,2175],{},"byPassAnomaliesFor"," cooldown. If the user completed MFA within the configured window, the session count check is skipped entirely. See ",[1006,2178,2180],{"href":2179},"#mfa-bypass-window","MFA Bypass Window",[856,2182,1663],{},[1016,2184,2186],{"className":1018,"code":2185,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n  anomalyType: `more than X active sessions`,\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: user_id,\n  visitorId: visitor_id,\n})\n",[860,2187,2188,2202,2213,2223,2233,2243,2253,2263],{"__ignoreMap":1021},[1025,2189,2190,2192,2194,2196,2198,2200],{"class":1027,"line":1028},[1025,2191,1673],{"class":1039},[1025,2193,1144],{"class":1035},[1025,2195,1678],{"class":1106},[1025,2197,1169],{"class":1035},[1025,2199,1683],{"class":1039},[1025,2201,1686],{"class":1035},[1025,2203,2204,2206,2208,2211],{"class":1027,"line":1059},[1025,2205,1766],{"class":1039},[1025,2207,1506],{"class":1694},[1025,2209,2210],{"class":1052}," `more than X active sessions`",[1025,2212,1512],{"class":1035},[1025,2214,2215,2217,2219,2221],{"class":1027,"line":1065},[1025,2216,1782],{"class":1039},[1025,2218,1506],{"class":1694},[1025,2220,1305],{"class":1039},[1025,2222,1512],{"class":1035},[1025,2224,2225,2227,2229,2231],{"class":1027,"line":1113},[1025,2226,1793],{"class":1039},[1025,2228,1506],{"class":1694},[1025,2230,1724],{"class":1194},[1025,2232,1512],{"class":1035},[1025,2234,2235,2237,2239,2241],{"class":1027,"line":1126},[1025,2236,1804],{"class":1039},[1025,2238,1506],{"class":1694},[1025,2240,1952],{"class":1194},[1025,2242,1512],{"class":1035},[1025,2244,2245,2247,2249,2251],{"class":1027,"line":1138},[1025,2246,1959],{"class":1039},[1025,2248,1506],{"class":1694},[1025,2250,1964],{"class":1039},[1025,2252,1512],{"class":1035},[1025,2254,2255,2257,2259,2261],{"class":1027,"line":1159},[1025,2256,1971],{"class":1039},[1025,2258,1506],{"class":1694},[1025,2260,1976],{"class":1039},[1025,2262,1512],{"class":1035},[1025,2264,2265],{"class":1027,"line":1191},[1025,2266,1742],{"class":1035},[1581,2268,2270],{"id":2269},"_5-rapid-token-creation","5. Rapid token creation",[856,2272,2273],{},"If the user has created more than three valid tokens in the last 10 minutes, the current token is revoked immediately. This pattern indicates an automated attack such as a race condition exploit or a token-farming script. No MFA is offered because the speed of creation suggests a non-human actor.",[915,2275,2276,2290],{},[918,2277,2278],{},[921,2279,2280,2284,2288],{},[924,2281,2282],{},[860,2283,1075],{},[924,2285,2286],{},[860,2287,881],{},[924,2289,1613],{},[938,2291,2292],{},[921,2293,2294,2298,2302],{},[943,2295,2296],{},[860,2297,894],{},[943,2299,2300],{},[860,2301,894],{},[943,2303,2304],{},[860,2305,2306],{},"3 tokens in less than 10 min",[856,2308,1663],{},[1016,2310,2312],{"className":1018,"code":2311,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n  anomalyType: `3 tokens in less than 10 min`,\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: false,\n})\n",[860,2313,2314,2328,2339,2349,2359,2369],{"__ignoreMap":1021},[1025,2315,2316,2318,2320,2322,2324,2326],{"class":1027,"line":1028},[1025,2317,1673],{"class":1039},[1025,2319,1144],{"class":1035},[1025,2321,1678],{"class":1106},[1025,2323,1169],{"class":1035},[1025,2325,1683],{"class":1039},[1025,2327,1686],{"class":1035},[1025,2329,2330,2332,2334,2337],{"class":1027,"line":1059},[1025,2331,1766],{"class":1039},[1025,2333,1506],{"class":1694},[1025,2335,2336],{"class":1052}," `3 tokens in less than 10 min`",[1025,2338,1512],{"class":1035},[1025,2340,2341,2343,2345,2347],{"class":1027,"line":1065},[1025,2342,1782],{"class":1039},[1025,2344,1506],{"class":1694},[1025,2346,1305],{"class":1039},[1025,2348,1512],{"class":1035},[1025,2350,2351,2353,2355,2357],{"class":1027,"line":1113},[1025,2352,1793],{"class":1039},[1025,2354,1506],{"class":1694},[1025,2356,1724],{"class":1194},[1025,2358,1512],{"class":1035},[1025,2360,2361,2363,2365,2367],{"class":1027,"line":1126},[1025,2362,1804],{"class":1039},[1025,2364,1506],{"class":1694},[1025,2366,1724],{"class":1194},[1025,2368,1512],{"class":1035},[1025,2370,2371],{"class":1027,"line":1138},[1025,2372,1742],{"class":1035},[1581,2374,2376],{"id":2375},"_6-ip-range-mismatch","6. IP range mismatch",[856,2378,2379,2380,1824,2383,2385,2386,2389],{},"The incoming IP address is compared against the stored ",[860,2381,2382],{},"ip_address",[860,2384,874],{}," table using the ",[860,2387,2388],{},"ip-range-check"," library. If the current IP does not fall within the same range as the stored address, MFA is triggered. This catches IP changes caused by VPN switches or network hops while allowing normal ISP rotation within the same subnet.",[915,2391,2392,2406],{},[918,2393,2394],{},[921,2395,2396,2400,2404],{},[924,2397,2398],{},[860,2399,1075],{},[924,2401,2402],{},[860,2403,881],{},[924,2405,1613],{},[938,2407,2408],{},[921,2409,2410,2414,2418],{},[943,2411,2412],{},[860,2413,894],{},[943,2415,2416],{},[860,2417,888],{},[943,2419,2420],{},[860,2421,2422],{},"Ip does not match",[856,2424,1663],{},[1016,2426,2428],{"className":1018,"code":2427,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n  anomalyType: 'Ip does not match',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n",[860,2429,2430,2444,2458,2468,2478,2488,2504,2518],{"__ignoreMap":1021},[1025,2431,2432,2434,2436,2438,2440,2442],{"class":1027,"line":1028},[1025,2433,1673],{"class":1039},[1025,2435,1144],{"class":1035},[1025,2437,1678],{"class":1106},[1025,2439,1169],{"class":1035},[1025,2441,1683],{"class":1039},[1025,2443,1686],{"class":1035},[1025,2445,2446,2448,2450,2452,2454,2456],{"class":1027,"line":1059},[1025,2447,1766],{"class":1039},[1025,2449,1506],{"class":1694},[1025,2451,1049],{"class":1048},[1025,2453,2422],{"class":1052},[1025,2455,1172],{"class":1048},[1025,2457,1512],{"class":1035},[1025,2459,2460,2462,2464,2466],{"class":1027,"line":1065},[1025,2461,1782],{"class":1039},[1025,2463,1506],{"class":1694},[1025,2465,1305],{"class":1039},[1025,2467,1512],{"class":1035},[1025,2469,2470,2472,2474,2476],{"class":1027,"line":1113},[1025,2471,1793],{"class":1039},[1025,2473,1506],{"class":1694},[1025,2475,1724],{"class":1194},[1025,2477,1512],{"class":1035},[1025,2479,2480,2482,2484,2486],{"class":1027,"line":1126},[1025,2481,1804],{"class":1039},[1025,2483,1506],{"class":1694},[1025,2485,1952],{"class":1194},[1025,2487,1512],{"class":1035},[1025,2489,2490,2492,2494,2497,2499,2502],{"class":1027,"line":1138},[1025,2491,1959],{"class":1039},[1025,2493,1506],{"class":1694},[1025,2495,2496],{"class":1039}," tokenResults",[1025,2498,1144],{"class":1035},[1025,2500,2501],{"class":1039},"user_id",[1025,2503,1512],{"class":1035},[1025,2505,2506,2508,2510,2512,2514,2516],{"class":1027,"line":1159},[1025,2507,1971],{"class":1039},[1025,2509,1506],{"class":1694},[1025,2511,2496],{"class":1039},[1025,2513,1144],{"class":1035},[1025,2515,1426],{"class":1039},[1025,2517,1512],{"class":1035},[1025,2519,2520],{"class":1027,"line":1191},[1025,2521,1742],{"class":1035},[1581,2523,2525],{"id":2524},"_7-suspicious-activity-score","7. Suspicious activity score",[856,2527,2528,2529,2531,2532,2536,2537,2540,2541,2544],{},"Every visitor accumulates a ",[860,2530,1441],{}," through the ",[1006,2533,2535],{"href":2534},"\u002Fdocs\u002Fbot-detection\u002Fguides\u002FSCORE","Bot Detector scoring pipeline",". The anomaly engine triggers MFA when the score reaches or exceeds ",[1410,2538,2539],{},"25%"," of the configured ",[860,2542,2543],{},"banScore",". The 25% threshold is intentional: MFA challenges start well before the visitor reaches the ban threshold, giving legitimate users a chance to prove themselves while slowing down attackers.",[915,2546,2547,2561],{},[918,2548,2549],{},[921,2550,2551,2555,2559],{},[924,2552,2553],{},[860,2554,1075],{},[924,2556,2557],{},[860,2558,881],{},[924,2560,1613],{},[938,2562,2563],{},[921,2564,2565,2569,2573],{},[943,2566,2567],{},[860,2568,894],{},[943,2570,2571],{},[860,2572,888],{},[943,2574,2575],{},[860,2576,2577],{},"Suspicion score to high",[856,2579,1319,2580,2582,2583,2586,2587,2590,2591,2594,2595,2598],{},[860,2581,2543],{}," defaults to ",[860,2584,2585],{},"100"," and is configurable through the ",[860,2588,2589],{},"botDetector.settings.banScore"," option. At the default, MFA triggers at a score of ",[860,2592,2593],{},"25",". See ",[1006,2596,2597],{"href":514},"Bot Detector Configuration"," for the full scoring options.",[856,2600,1663],{},[1016,2602,2604],{"className":1018,"code":2603,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n  anomalyType: 'Suspicion score to high',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n",[860,2605,2606,2620,2634,2644,2654,2664,2678,2692],{"__ignoreMap":1021},[1025,2607,2608,2610,2612,2614,2616,2618],{"class":1027,"line":1028},[1025,2609,1673],{"class":1039},[1025,2611,1144],{"class":1035},[1025,2613,1678],{"class":1106},[1025,2615,1169],{"class":1035},[1025,2617,1683],{"class":1039},[1025,2619,1686],{"class":1035},[1025,2621,2622,2624,2626,2628,2630,2632],{"class":1027,"line":1059},[1025,2623,1766],{"class":1039},[1025,2625,1506],{"class":1694},[1025,2627,1049],{"class":1048},[1025,2629,2577],{"class":1052},[1025,2631,1172],{"class":1048},[1025,2633,1512],{"class":1035},[1025,2635,2636,2638,2640,2642],{"class":1027,"line":1065},[1025,2637,1782],{"class":1039},[1025,2639,1506],{"class":1694},[1025,2641,1305],{"class":1039},[1025,2643,1512],{"class":1035},[1025,2645,2646,2648,2650,2652],{"class":1027,"line":1113},[1025,2647,1793],{"class":1039},[1025,2649,1506],{"class":1694},[1025,2651,1724],{"class":1194},[1025,2653,1512],{"class":1035},[1025,2655,2656,2658,2660,2662],{"class":1027,"line":1126},[1025,2657,1804],{"class":1039},[1025,2659,1506],{"class":1694},[1025,2661,1952],{"class":1194},[1025,2663,1512],{"class":1035},[1025,2665,2666,2668,2670,2672,2674,2676],{"class":1027,"line":1138},[1025,2667,1959],{"class":1039},[1025,2669,1506],{"class":1694},[1025,2671,2496],{"class":1039},[1025,2673,1144],{"class":1035},[1025,2675,2501],{"class":1039},[1025,2677,1512],{"class":1035},[1025,2679,2680,2682,2684,2686,2688,2690],{"class":1027,"line":1159},[1025,2681,1971],{"class":1039},[1025,2683,1506],{"class":1694},[1025,2685,2496],{"class":1039},[1025,2687,1144],{"class":1035},[1025,2689,1426],{"class":1039},[1025,2691,1512],{"class":1035},[1025,2693,2694],{"class":1027,"line":1191},[1025,2695,1742],{"class":1035},[1581,2697,2699],{"id":2698},"_8-proxy-and-hosting-detection","8. Proxy and hosting detection",[856,2701,2702,2703,2706,2707,2709,2710,1645,2713,2716,2717,2719,2720,2722,2723,2726],{},"The incoming IP is geolocated by calling ",[860,2704,2705],{},"getGeoData()"," from the ",[1006,2708,399],{"href":35},". This function queries the local MMDB databases and returns geo information along with ",[860,2711,2712],{},"proxy",[860,2714,2715],{},"hosting"," boolean flags. The ",[860,2718,2715],{}," flag is ",[860,2721,888],{}," when the ASN classification is ",[860,2724,2725],{},"\"Content\""," or when the IP belongs to a known Tor exit node.",[856,2728,2729,2730,1645,2733,2736,2737,2739],{},"If the request arrives through a proxy or hosting provider, the service checks the visitor's ",[860,2731,2732],{},"proxy_allowed",[860,2734,2735],{},"hosting_allowed"," flags in the ",[860,2738,874],{}," table.",[915,2741,2742,2760],{},[918,2743,2744],{},[921,2745,2746,2749,2752,2756],{},[924,2747,2748],{},"Incoming",[924,2750,2751],{},"Stored flag",[924,2753,2754],{},[860,2755,1075],{},[924,2757,2758],{},[860,2759,881],{},[938,2761,2762,2780,2797,2815],{},[921,2763,2764,2767,2772,2776],{},[943,2765,2766],{},"Proxy",[943,2768,2769],{},[860,2770,2771],{},"proxy_allowed: true",[943,2773,2774],{},[860,2775,888],{},[943,2777,2778],{},[860,2779,894],{},[921,2781,2782,2784,2789,2793],{},[943,2783,2766],{},[943,2785,2786],{},[860,2787,2788],{},"proxy_allowed: false",[943,2790,2791],{},[860,2792,894],{},[943,2794,2795],{},[860,2796,888],{},[921,2798,2799,2802,2807,2811],{},[943,2800,2801],{},"Hosting",[943,2803,2804],{},[860,2805,2806],{},"hosting_allowed: true",[943,2808,2809],{},[860,2810,888],{},[943,2812,2813],{},[860,2814,894],{},[921,2816,2817,2819,2824,2828],{},[943,2818,2801],{},[943,2820,2821],{},[860,2822,2823],{},"hosting_allowed: false",[943,2825,2826],{},[860,2827,894],{},[943,2829,2830],{},[860,2831,888],{},[1637,2833,2834],{},[856,2835,2836,2837,2839,2840,2843,2844,2847],{},"When a proxy or hosting request is allowed (flags are ",[860,2838,888],{},"), the function returns ",[860,2841,2842],{},"valid: true"," immediately and ",[1410,2845,2846],{},"skips the fingerprint consistency loop"," (check 9). This is by design: proxy and hosting environments often rotate IPs and geo data frequently, which would cause false positives in the fingerprint comparison. Once a user has been MFA-verified on a proxy connection, subsequent requests from any proxy are trusted without field-by-field comparison.",[856,2849,2850,2851,1824,2853,2855,2856,2858,2859,2862,2863,1645,2865,2867,2868,2870],{},"Both flags default to ",[860,2852,894],{},[860,2854,874],{}," table. They are set to ",[860,2857,888],{}," during ",[1006,2860,2861],{"href":124},"MFA verification",". When a user completes an MFA challenge that was triggered by proxy or hosting detection, the ",[860,2864,2732],{},[860,2866,2735],{}," columns are both updated to ",[860,2869,888],{}," on the visitor record. This means MFA is a one-time gate per device for proxy\u002Fhosting users.",[856,2872,1663],{},[1016,2874,2876],{"className":1018,"code":2875,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n  anomalyType: 'Proxy Or hosting',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n",[860,2877,2878,2892,2907,2917,2927,2937,2951,2965],{"__ignoreMap":1021},[1025,2879,2880,2882,2884,2886,2888,2890],{"class":1027,"line":1028},[1025,2881,1673],{"class":1039},[1025,2883,1144],{"class":1035},[1025,2885,1678],{"class":1106},[1025,2887,1169],{"class":1035},[1025,2889,1683],{"class":1039},[1025,2891,1686],{"class":1035},[1025,2893,2894,2896,2898,2900,2903,2905],{"class":1027,"line":1059},[1025,2895,1766],{"class":1039},[1025,2897,1506],{"class":1694},[1025,2899,1049],{"class":1048},[1025,2901,2902],{"class":1052},"Proxy Or hosting",[1025,2904,1172],{"class":1048},[1025,2906,1512],{"class":1035},[1025,2908,2909,2911,2913,2915],{"class":1027,"line":1065},[1025,2910,1782],{"class":1039},[1025,2912,1506],{"class":1694},[1025,2914,1305],{"class":1039},[1025,2916,1512],{"class":1035},[1025,2918,2919,2921,2923,2925],{"class":1027,"line":1113},[1025,2920,1793],{"class":1039},[1025,2922,1506],{"class":1694},[1025,2924,1724],{"class":1194},[1025,2926,1512],{"class":1035},[1025,2928,2929,2931,2933,2935],{"class":1027,"line":1126},[1025,2930,1804],{"class":1039},[1025,2932,1506],{"class":1694},[1025,2934,1952],{"class":1194},[1025,2936,1512],{"class":1035},[1025,2938,2939,2941,2943,2945,2947,2949],{"class":1027,"line":1138},[1025,2940,1959],{"class":1039},[1025,2942,1506],{"class":1694},[1025,2944,2496],{"class":1039},[1025,2946,1144],{"class":1035},[1025,2948,2501],{"class":1039},[1025,2950,1512],{"class":1035},[1025,2952,2953,2955,2957,2959,2961,2963],{"class":1027,"line":1159},[1025,2954,1971],{"class":1039},[1025,2956,1506],{"class":1694},[1025,2958,2496],{"class":1039},[1025,2960,1144],{"class":1035},[1025,2962,1426],{"class":1039},[1025,2964,1512],{"class":1035},[1025,2966,2967],{"class":1027,"line":1191},[1025,2968,1742],{"class":1035},[1016,2970,2972],{"className":1018,"code":2971,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n  anomalyType: 'Proxy or hosting allowed',\n  canaryCookie: cookie,\n  resolved: true,\n  resolvable: false,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n",[860,2973,2974,2988,3003,3013,3023,3033,3047,3061],{"__ignoreMap":1021},[1025,2975,2976,2978,2980,2982,2984,2986],{"class":1027,"line":1028},[1025,2977,1673],{"class":1039},[1025,2979,1144],{"class":1035},[1025,2981,1678],{"class":1106},[1025,2983,1169],{"class":1035},[1025,2985,1683],{"class":1039},[1025,2987,1686],{"class":1035},[1025,2989,2990,2992,2994,2996,2999,3001],{"class":1027,"line":1059},[1025,2991,1766],{"class":1039},[1025,2993,1506],{"class":1694},[1025,2995,1049],{"class":1048},[1025,2997,2998],{"class":1052},"Proxy or hosting allowed",[1025,3000,1172],{"class":1048},[1025,3002,1512],{"class":1035},[1025,3004,3005,3007,3009,3011],{"class":1027,"line":1065},[1025,3006,1782],{"class":1039},[1025,3008,1506],{"class":1694},[1025,3010,1305],{"class":1039},[1025,3012,1512],{"class":1035},[1025,3014,3015,3017,3019,3021],{"class":1027,"line":1113},[1025,3016,1793],{"class":1039},[1025,3018,1506],{"class":1694},[1025,3020,1952],{"class":1194},[1025,3022,1512],{"class":1035},[1025,3024,3025,3027,3029,3031],{"class":1027,"line":1126},[1025,3026,1804],{"class":1039},[1025,3028,1506],{"class":1694},[1025,3030,1724],{"class":1194},[1025,3032,1512],{"class":1035},[1025,3034,3035,3037,3039,3041,3043,3045],{"class":1027,"line":1138},[1025,3036,1959],{"class":1039},[1025,3038,1506],{"class":1694},[1025,3040,2496],{"class":1039},[1025,3042,1144],{"class":1035},[1025,3044,2501],{"class":1039},[1025,3046,1512],{"class":1035},[1025,3048,3049,3051,3053,3055,3057,3059],{"class":1027,"line":1159},[1025,3050,1971],{"class":1039},[1025,3052,1506],{"class":1694},[1025,3054,2496],{"class":1039},[1025,3056,1144],{"class":1035},[1025,3058,1426],{"class":1039},[1025,3060,1512],{"class":1035},[1025,3062,3063],{"class":1027,"line":1191},[1025,3064,1742],{"class":1035},[1581,3066,3068],{"id":3067},"_9-device-fingerprint-consistency-loop","9. Device fingerprint consistency loop",[856,3070,3071,3072,3075,3076,3079],{},"The final check runs only when the request is not coming through a proxy or hosting provider (or when the user's flags have not been set yet). The function builds a fingerprint object by merging the incoming geo data (from ",[860,3073,3074],{},"getGeoData",") with the parsed User-Agent fields (from ",[860,3077,3078],{},"parseUA",") and iterates over every key in the result. For each key that also exists in the stored visitor record, it compares the values.",[856,3081,3082,3095],{},[1410,3083,3084,3085,867,3088,3091,3092,1144],{},"The comparison skips a field if either side is ",[860,3086,3087],{},"null",[860,3089,3090],{},"undefined",", or the string ",[860,3093,3094],{},"'unknown'"," This prevents false positives on fields that were not populated during the initial fingerprint capture.",[856,3097,3098],{},"Compared fields include:",[915,3100,3101,3111],{},[918,3102,3103],{},[921,3104,3105,3108],{},[924,3106,3107],{},"Category",[924,3109,3110],{},"Fields",[938,3112,3113,3150,3166],{},[921,3114,3115,3118],{},[943,3116,3117],{},"Geo",[943,3119,3120,867,3123,867,3126,867,3129,867,3132,867,3135,867,3138,867,3141,867,3144,867,3147],{},[860,3121,3122],{},"country",[860,3124,3125],{},"city",[860,3127,3128],{},"district",[860,3130,3131],{},"lat",[860,3133,3134],{},"lon",[860,3136,3137],{},"timezone",[860,3139,3140],{},"currency",[860,3142,3143],{},"isp",[860,3145,3146],{},"org",[860,3148,3149],{},"as_org",[921,3151,3152,3155],{},[943,3153,3154],{},"Device",[943,3156,3157,867,3160,867,3163],{},[860,3158,3159],{},"device",[860,3161,3162],{},"deviceVendor",[860,3164,3165],{},"deviceModel",[921,3167,3168,3171],{},[943,3169,3170],{},"Browser",[943,3172,3173,867,3176,867,3179,867,3182],{},[860,3174,3175],{},"browser",[860,3177,3178],{},"browserType",[860,3180,3181],{},"browserVersion",[860,3183,3184],{},"os",[856,3186,3187],{},"Any single mismatch on a populated field triggers MFA.",[915,3189,3190,3204],{},[918,3191,3192],{},[921,3193,3194,3198,3202],{},[924,3195,3196],{},[860,3197,1075],{},[924,3199,3200],{},[860,3201,881],{},[924,3203,1613],{},[938,3205,3206],{},[921,3207,3208,3212,3216],{},[943,3209,3210],{},[860,3211,894],{},[943,3213,3214],{},[860,3215,888],{},[943,3217,3218],{},[860,3219,3220],{},"Loop detected",[856,3222,1663],{},[1016,3224,3226],{"className":1018,"code":3225,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n  anomalyType: 'Loop detected',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n",[860,3227,3228,3242,3256,3266,3276,3286,3300,3314],{"__ignoreMap":1021},[1025,3229,3230,3232,3234,3236,3238,3240],{"class":1027,"line":1028},[1025,3231,1673],{"class":1039},[1025,3233,1144],{"class":1035},[1025,3235,1678],{"class":1106},[1025,3237,1169],{"class":1035},[1025,3239,1683],{"class":1039},[1025,3241,1686],{"class":1035},[1025,3243,3244,3246,3248,3250,3252,3254],{"class":1027,"line":1059},[1025,3245,1766],{"class":1039},[1025,3247,1506],{"class":1694},[1025,3249,1049],{"class":1048},[1025,3251,3220],{"class":1052},[1025,3253,1172],{"class":1048},[1025,3255,1512],{"class":1035},[1025,3257,3258,3260,3262,3264],{"class":1027,"line":1065},[1025,3259,1782],{"class":1039},[1025,3261,1506],{"class":1694},[1025,3263,1305],{"class":1039},[1025,3265,1512],{"class":1035},[1025,3267,3268,3270,3272,3274],{"class":1027,"line":1113},[1025,3269,1793],{"class":1039},[1025,3271,1506],{"class":1694},[1025,3273,1724],{"class":1194},[1025,3275,1512],{"class":1035},[1025,3277,3278,3280,3282,3284],{"class":1027,"line":1126},[1025,3279,1804],{"class":1039},[1025,3281,1506],{"class":1694},[1025,3283,1952],{"class":1194},[1025,3285,1512],{"class":1035},[1025,3287,3288,3290,3292,3294,3296,3298],{"class":1027,"line":1138},[1025,3289,1959],{"class":1039},[1025,3291,1506],{"class":1694},[1025,3293,2496],{"class":1039},[1025,3295,1144],{"class":1035},[1025,3297,2501],{"class":1039},[1025,3299,1512],{"class":1035},[1025,3301,3302,3304,3306,3308,3310,3312],{"class":1027,"line":1159},[1025,3303,1971],{"class":1039},[1025,3305,1506],{"class":1694},[1025,3307,2496],{"class":1039},[1025,3309,1144],{"class":1035},[1025,3311,1426],{"class":1039},[1025,3313,1512],{"class":1035},[1025,3315,3316],{"class":1027,"line":1191},[1025,3317,1742],{"class":1035},[856,3319,3320,3321,3324],{},"If all nine checks pass, the function returns ",[860,3322,3323],{},"{ valid: true, reason: 'Checks passed', reqMFA: false }",",",[856,3326,3327],{},"and sets:",[1016,3329,3331],{"className":1018,"code":3330,"language":1020,"meta":1021,"style":1021},"cache.set(hashedClientToken, {\n  anomalyType: 'Checks passed',\n  canaryCookie: cookie,\n  resolved: true,\n  resolvable: false,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n",[860,3332,3333,3347,3362,3372,3382,3392,3406,3420],{"__ignoreMap":1021},[1025,3334,3335,3337,3339,3341,3343,3345],{"class":1027,"line":1028},[1025,3336,1673],{"class":1039},[1025,3338,1144],{"class":1035},[1025,3340,1678],{"class":1106},[1025,3342,1169],{"class":1035},[1025,3344,1683],{"class":1039},[1025,3346,1686],{"class":1035},[1025,3348,3349,3351,3353,3355,3358,3360],{"class":1027,"line":1059},[1025,3350,1766],{"class":1039},[1025,3352,1506],{"class":1694},[1025,3354,1049],{"class":1048},[1025,3356,3357],{"class":1052},"Checks passed",[1025,3359,1172],{"class":1048},[1025,3361,1512],{"class":1035},[1025,3363,3364,3366,3368,3370],{"class":1027,"line":1065},[1025,3365,1782],{"class":1039},[1025,3367,1506],{"class":1694},[1025,3369,1305],{"class":1039},[1025,3371,1512],{"class":1035},[1025,3373,3374,3376,3378,3380],{"class":1027,"line":1113},[1025,3375,1793],{"class":1039},[1025,3377,1506],{"class":1694},[1025,3379,1952],{"class":1194},[1025,3381,1512],{"class":1035},[1025,3383,3384,3386,3388,3390],{"class":1027,"line":1126},[1025,3385,1804],{"class":1039},[1025,3387,1506],{"class":1694},[1025,3389,1724],{"class":1194},[1025,3391,1512],{"class":1035},[1025,3393,3394,3396,3398,3400,3402,3404],{"class":1027,"line":1138},[1025,3395,1959],{"class":1039},[1025,3397,1506],{"class":1694},[1025,3399,2496],{"class":1039},[1025,3401,1144],{"class":1035},[1025,3403,2501],{"class":1039},[1025,3405,1512],{"class":1035},[1025,3407,3408,3410,3412,3414,3416,3418],{"class":1027,"line":1159},[1025,3409,1971],{"class":1039},[1025,3411,1506],{"class":1694},[1025,3413,2496],{"class":1039},[1025,3415,1144],{"class":1035},[1025,3417,1426],{"class":1039},[1025,3419,1512],{"class":1035},[1025,3421,3422],{"class":1027,"line":1191},[1025,3423,1742],{"class":1035},[897,3425],{},[900,3427,3429],{"id":3428},"device-fingerprinting-and-the-bot-detector","Device fingerprinting and the Bot Detector",[856,3431,3432,3433,3435,3436,3438,3439,3442],{},"The anomaly engine relies on the ",[860,3434,874],{}," table, which is created and managed by the ",[1006,3437,399],{"href":404}," module. The Bot Detector runs as Express middleware (",[860,3440,3441],{},"detectBots",") before any auth routes and is responsible for:",[3444,3445,3446,3452,3455,3458,3461],"ol",{},[1407,3447,3448,3449,3451],{},"Generating the ",[860,3450,1322],{}," cookie on first contact",[1407,3453,3454],{},"Resolving geo data from the client IP via local MMDB databases",[1407,3456,3457],{},"Parsing the User-Agent header into structured device, browser, and OS fields",[1407,3459,3460],{},"Upserting the visitor record with all fingerprint fields",[1407,3462,3463,3464,2531,3466],{},"Accumulating a ",[860,3465,1441],{},[1006,3467,3468],{"href":435},"checker pipeline",[856,3470,3471,3472,1506],{},"The auth service imports three helpers directly from ",[860,3473,3474],{},"@riavzon\u002Fbot-detector",[915,3476,3477,3489],{},[918,3478,3479],{},[921,3480,3481,3484,3486],{},[924,3482,3483],{},"Helper",[924,3485,936],{},[924,3487,3488],{},"Used by",[938,3490,3491,3519,3552],{},[921,3492,3493,3498,3510],{},[943,3494,3495],{},[860,3496,3497],{},"getGeoData(ip)",[943,3499,3500,3501,3504,3505,871,3507,3509],{},"Returns a ",[860,3502,3503],{},"GeoResponse"," object with geo fields, ",[860,3506,2712],{},[860,3508,2715],{}," booleans",[943,3511,3512,3514,3515,3518],{},[860,3513,908],{}," (checks 8 and 9), ",[860,3516,3517],{},"getFingerPrint"," middleware",[921,3520,3521,3526,3545],{},[943,3522,3523],{},[860,3524,3525],{},"parseUA(ua)",[943,3527,3500,3528,3531,3532,867,3534,867,3536,867,3538,867,3541,3544],{},[860,3529,3530],{},"ParsedUAResult"," with ",[860,3533,3159],{},[860,3535,3175],{},[860,3537,3184],{},[860,3539,3540],{},"bot",[860,3542,3543],{},"botAI",", and vendor info",[943,3546,3547,3549,3550,3518],{},[860,3548,908],{}," (check 9), ",[860,3551,3517],{},[921,3553,3554,3559,3562],{},[943,3555,3556],{},[860,3557,3558],{},"updateVisitors(data, cookie, visitorId)",[943,3560,3561],{},"Updates the visitor row with fresh fingerprint fields",[943,3563,3564,3567],{},[860,3565,3566],{},"trustVisitor"," (called during login)",[856,3569,1319,3570,3572,3573,1645,3575,3577,3578,3581,3582,1645,3584,3586,3587,3589,3590,3592],{},[860,3571,3517],{}," middleware in the auth service calls both ",[860,3574,3074],{},[860,3576,3078],{}," and attaches the merged result to ",[860,3579,3580],{},"req.fingerPrint",". This middleware runs in the rotation and MFA routes so that the caller has access to the current fingerprint data. The anomaly engine itself calls ",[860,3583,3074],{},[860,3585,3078],{}," independently for checks 8 and 9 rather than reading from ",[860,3588,3580],{},", because ",[860,3591,908],{}," receives the raw IP and User-Agent as parameters and does its own resolution.",[1581,3594,1327],{"id":3595},"canary_id-lifecycle",[856,3597,1319,3598,3600],{},[860,3599,1322],{}," cookie is the thread that ties a browser session to a visitor record across authentication events. Here is how it flows:",[3602,3603,3605,3610,3620,3623,3655,3659,3670,3674],"steps",{"level":3604},"4",[3606,3607,3609],"h4",{"id":3608},"first-visit","First visit",[856,3611,3612,3613,3616,3617,3619],{},"The Bot Detector middleware generates a 64-character hex string using ",[860,3614,3615],{},"randomBytes(32)"," and sets it as the ",[860,3618,1322],{}," cookie. A visitor row is inserted with this value as the primary key, along with the initial geo and UA fingerprint.",[3606,3621,103],{"id":3622},"login",[856,3624,3625,3626,3628,3629,3632,3633,3636,3637,3639,3640,3643,3644,3647,3648,3650,3651,1144],{},"The login controller reads the ",[860,3627,1322],{}," from ",[860,3630,3631],{},"req.cookies",". If ",[860,3634,3635],{},"trustUserDeviceOnAuth"," is enabled and the bot-detector assigned a new ",[860,3638,1426],{}," for this request (",[860,3641,3642],{},"req.newVisitorId","), the controller calls ",[860,3645,3646],{},"trustVisitor()"," to update the user's ",[860,3649,1426],{}," foreign key to point at the new visitor row. This re-baselines the fingerprint so that subsequent anomaly checks compare against the device the user actually logged in from. See ",[1006,3652,3654],{"href":3653},"#trusted-devices-on-login","Trusted devices on login",[3606,3656,3658],{"id":3657},"token-rotation","Token rotation",[856,3660,1319,3661,3663,3664,3666,3667,3669],{},[860,3662,964],{}," controller passes the ",[860,3665,1322],{}," cookie to ",[860,3668,862],{}," where it is compared against the stored value (check 2). If it matches, the rest of the checks proceed. If it does not match, MFA is triggered.",[3606,3671,3673],{"id":3672},"mfa-completion","MFA completion",[856,3675,3676,3677,3679,3680,1645,3682,1448,3684,3686,3687,3689],{},"After a successful MFA challenge, the service updates ",[860,3678,1430],{}," on the user row and sets ",[860,3681,2732],{},[860,3683,2735],{},[860,3685,888],{}," on the visitor record. This allows subsequent proxy\u002Fhosting requests from the same ",[860,3688,1322],{}," to pass without further MFA.",[897,3691],{},[900,3693,3695],{"id":3694},"mfa-bypass-window","MFA bypass window",[856,3697,3698,3699,3701,3702,3704,3705,3707],{},"When a user completes MFA, the ",[860,3700,1430],{}," timestamp is updated in the ",[860,3703,870],{}," table. For the duration of ",[860,3706,2175],{}," milliseconds after that timestamp, check 4 (session count limit) is skipped. This prevents a user from being MFA-challenged again immediately after verifying a new device or session.",[1016,3709,3711],{"className":1018,"code":3710,"language":1020,"meta":1021,"style":1021},"jwt: {\n  refresh_tokens: {\n    byPassAnomaliesFor: 300_000   \u002F\u002F 5 minutes\n  }\n}\n",[860,3712,3713,3718,3723,3735,3740],{"__ignoreMap":1021},[1025,3714,3715],{"class":1027,"line":1028},[1025,3716,3717],{"class":1035},"jwt: {\n",[1025,3719,3720],{"class":1027,"line":1059},[1025,3721,3722],{"class":1035},"  refresh_tokens: {\n",[1025,3724,3725,3728,3732],{"class":1027,"line":1065},[1025,3726,3727],{"class":1035},"    byPassAnomaliesFor: ",[1025,3729,3731],{"class":3730},"spgvN","300_000",[1025,3733,3734],{"class":1122},"   \u002F\u002F 5 minutes\n",[1025,3736,3737],{"class":1027,"line":1113},[1025,3738,3739],{"class":1035},"  }\n",[1025,3741,3742],{"class":1027,"line":1126},[1025,3743,1269],{"class":1035},[856,3745,3746],{},"The bypass only applies to the session count check. All other checks still run regardless of when MFA was last completed.",[3748,3749,3750],"tip",{},[856,3751,3752,3753,1448,3755,3758,3759,3761],{},"When a refresh token expires, the verification functions set ",[860,3754,1430],{},[860,3756,3757],{},"NULL"," on the user row. This resets the ",[860,3760,2175],{}," cooldown window. The next time the user hits any anomaly condition that checks the bypass, it will not be skipped. A clean login from the same device with no anomalies still passes without MFA.",[897,3763],{},[900,3765,3654],{"id":3766},"trusted-devices-on-login",[856,3768,1319,3769,3771,3772,3774,3775,3777],{},[860,3770,3635],{}," configuration option controls whether the login controller re-baselines the user's visitor record. When set to ",[860,3773,888],{},", the login flow calls ",[860,3776,3646],{}," after a successful password check. This function:",[3444,3779,3780,3788],{},[1407,3781,3782,3783,3785,3786],{},"Updates ",[860,3784,1454],{}," to point at the visitor row associated with the current ",[860,3787,1322],{},[1407,3789,3790,3791,2706,3794,3796],{},"Calls ",[860,3792,3793],{},"updateVisitors()",[1006,3795,399],{"href":35}," to overwrite the visitor fingerprint with the current request's geo and UA data",[856,3798,3799],{},"The effect is that the stored fingerprint always reflects the device the user most recently logged in from. Without this option, the fingerprint stays fixed to whatever device was first associated with the user, which can cause false positives when users legitimately switch devices.",[856,3801,3802,3803,3805],{},"The same logic runs in the ",[1006,3804,111],{"href":112}," flow after a successful external provider callback.",[897,3807],{},[900,3809,3811],{"id":3810},"return-value","Return value",[1016,3813,3815],{"className":1018,"code":3814,"language":1020,"meta":1021,"style":1021},"interface AnomalyResult {\n  valid: boolean\n  reason: string        \u002F\u002F Human-readable description of the outcome\n  reqMFA: boolean       \u002F\u002F true = issue MFA; false = hard block or pass\n  userId?: number       \u002F\u002F Present when reqMFA is true\n  visitorId?: string    \u002F\u002F Present when reqMFA is true\n}\n",[860,3816,3817,3826,3836,3848,3860,3872,3883],{"__ignoreMap":1021},[1025,3818,3819,3821,3824],{"class":1027,"line":1028},[1025,3820,1491],{"class":1068},[1025,3822,3823],{"class":1494}," AnomalyResult",[1025,3825,1498],{"class":1035},[1025,3827,3828,3831,3833],{"class":1027,"line":1059},[1025,3829,3830],{"class":1039},"  valid",[1025,3832,1506],{"class":1099},[1025,3834,3835],{"class":1494}," boolean\n",[1025,3837,3838,3841,3843,3845],{"class":1027,"line":1065},[1025,3839,3840],{"class":1039},"  reason",[1025,3842,1506],{"class":1099},[1025,3844,1509],{"class":1494},[1025,3846,3847],{"class":1122},"        \u002F\u002F Human-readable description of the outcome\n",[1025,3849,3850,3853,3855,3857],{"class":1027,"line":1113},[1025,3851,3852],{"class":1039},"  reqMFA",[1025,3854,1506],{"class":1099},[1025,3856,1552],{"class":1494},[1025,3858,3859],{"class":1122},"       \u002F\u002F true = issue MFA; false = hard block or pass\n",[1025,3861,3862,3864,3866,3869],{"class":1027,"line":1126},[1025,3863,1959],{"class":1039},[1025,3865,1530],{"class":1099},[1025,3867,3868],{"class":1494}," number",[1025,3870,3871],{"class":1122},"       \u002F\u002F Present when reqMFA is true\n",[1025,3873,3874,3876,3878,3880],{"class":1027,"line":1138},[1025,3875,1971],{"class":1039},[1025,3877,1530],{"class":1099},[1025,3879,1509],{"class":1494},[1025,3881,3882],{"class":1122},"    \u002F\u002F Present when reqMFA is true\n",[1025,3884,3885],{"class":1027,"line":1159},[1025,3886,1269],{"class":1035},[856,3888,979,3889,885,3891,867,3893,1645,3895,3897,3898,3900],{},[860,3890,881],{},[860,3892,888],{},[860,3894,1089],{},[860,3896,1094],{}," are included so the caller can construct and send the MFA email without an additional database lookup. See ",[1006,3899,123],{"href":124}," for the full challenge and verification flow.",[856,3902,979,3903,885,3905,1645,3907,885,3909,3911],{},[860,3904,881],{},[860,3906,894],{},[860,3908,1075],{},[860,3910,894],{},", the session has been hard-blocked. The token is already revoked and the client must re-authenticate from scratch.",[897,3913],{},[900,3915,3917],{"id":3916},"decision-summary","Decision summary",[915,3919,3920,3937],{},[918,3921,3922],{},[921,3923,3924,3927,3930,3934],{},[924,3925,3926],{},"Check",[924,3928,3929],{},"Trigger",[924,3931,3932],{},[860,3933,881],{},[924,3935,3936],{},"Rationale",[938,3938,3939,3954,3971,3988,4005,4020,4035,4053,4068],{},[921,3940,3941,3944,3947,3951],{},[943,3942,3943],{},"1. Token validity",[943,3945,3946],{},"Token missing, revoked, or replayed",[943,3948,3949],{},[860,3950,894],{},[943,3952,3953],{},"No identity to challenge. Session is dead.",[921,3955,3956,3959,3964,3968],{},[943,3957,3958],{},"2. Canary mismatch",[943,3960,3961,3962,1305],{},"Different ",[860,3963,1322],{},[943,3965,3966],{},[860,3967,888],{},[943,3969,3970],{},"New device. User can prove identity.",[921,3972,3973,3976,3981,3985],{},[943,3974,3975],{},"3. Idle",[943,3977,3978,3980],{},[860,3979,1992],{}," older than 24 hours",[943,3982,3983],{},[860,3984,888],{},[943,3986,3987],{},"Stale session. Re-verify before continuing.",[921,3989,3990,3993,3998,4002],{},[943,3991,3992],{},"4. Session count",[943,3994,3995],{},[860,3996,3997],{},">= maxAllowedSessionsPerUser",[943,3999,4000],{},[860,4001,888],{},[943,4003,4004],{},"Too many devices. User picks which to keep.",[921,4006,4007,4010,4013,4017],{},[943,4008,4009],{},"5. Rapid creation",[943,4011,4012],{},"More than 3 tokens in 10 minutes",[943,4014,4015],{},[860,4016,894],{},[943,4018,4019],{},"Automated behavior. Hard block.",[921,4021,4022,4025,4028,4032],{},[943,4023,4024],{},"6. IP range",[943,4026,4027],{},"IP outside stored range",[943,4029,4030],{},[860,4031,888],{},[943,4033,4034],{},"Network change. User can confirm.",[921,4036,4037,4040,4046,4050],{},[943,4038,4039],{},"7. Suspicion score",[943,4041,4042,4043],{},"Score ",[860,4044,4045],{},">= banScore * 0.25",[943,4047,4048],{},[860,4049,888],{},[943,4051,4052],{},"Bot-like behavior building up. Early challenge.",[921,4054,4055,4058,4061,4065],{},[943,4056,4057],{},"8. Proxy\u002Fhosting",[943,4059,4060],{},"Proxy or hosting without allow flag",[943,4062,4063],{},[860,4064,888],{},[943,4066,4067],{},"New infrastructure. One-time MFA gate.",[921,4069,4070,4073,4076,4080],{},[943,4071,4072],{},"9. Fingerprint",[943,4074,4075],{},"Any geo or UA field mismatch",[943,4077,4078],{},[860,4079,888],{},[943,4081,4082],{},"Environment changed. User can re-verify.",[897,4084],{},[900,4086,4088],{"id":4087},"configuration-reference","Configuration reference",[856,4090,4091],{},"All options that affect anomaly detection:",[915,4093,4094,4111],{},[918,4095,4096],{},[921,4097,4098,4101,4104,4106,4109],{},[924,4099,4100],{},"Option",[924,4102,4103],{},"Location",[924,4105,1281],{},[924,4107,4108],{},"Default",[924,4110,1284],{},[938,4112,4113,4135,4157,4178,4200],{},[921,4114,4115,4119,4124,4129,4132],{},[943,4116,4117],{},[860,4118,2135],{},[943,4120,4121],{},[860,4122,4123],{},"jwt.refresh_tokens",[943,4125,4126],{},[860,4127,4128],{},"number",[943,4130,4131],{},"—",[943,4133,4134],{},"Maximum concurrent valid refresh tokens before check 4 triggers",[921,4136,4137,4141,4145,4149,4151],{},[943,4138,4139],{},[860,4140,2175],{},[943,4142,4143],{},[860,4144,4123],{},[943,4146,4147],{},[860,4148,4128],{},[943,4150,4131],{},[943,4152,4153,4154,4156],{},"Milliseconds after ",[860,4155,1430],{}," during which session count check is skipped",[921,4158,4159,4164,4169,4173,4175],{},[943,4160,4161],{},[860,4162,4163],{},"enableBotDetector",[943,4165,4166],{},[860,4167,4168],{},"botDetector",[943,4170,4171],{},[860,4172,1372],{},[943,4174,4131],{},[943,4176,4177],{},"Enables or disables the Bot Detector integration entirely",[921,4179,4180,4184,4189,4193,4197],{},[943,4181,4182],{},[860,4183,2543],{},[943,4185,4186],{},[860,4187,4188],{},"botDetector.settings",[943,4190,4191],{},[860,4192,4128],{},[943,4194,4195],{},[860,4196,2585],{},[943,4198,4199],{},"Score threshold for banning a visitor. MFA triggers at 25% of this value",[921,4201,4202,4206,4209,4213,4217],{},[943,4203,4204],{},[860,4205,3635],{},[943,4207,4208],{},"Root config",[943,4210,4211],{},[860,4212,1372],{},[943,4214,4215],{},[860,4216,894],{},[943,4218,4219],{},"Whether login re-baselines the visitor fingerprint to the current device",[856,4221,1004,4222,4224,4225,4227],{},[1006,4223,237],{"href":238}," for the full schema reference and ",[1006,4226,2597],{"href":514}," for the scoring and checker pipeline options.",[4229,4230,4231],"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 .sghk6, html code.shiki .sghk6{--shiki-light:#008000;--shiki-default:#008000;--shiki-dark:#6272A4}html pre.shiki code .sjR7W, html code.shiki .sjR7W{--shiki-light:#0000FF;--shiki-default:#0000FF;--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 .sFs1U, html code.shiki .sFs1U{--shiki-light:#267F99;--shiki-light-font-style:inherit;--shiki-default:#267F99;--shiki-default-font-style:inherit;--shiki-dark:#8BE9FD;--shiki-dark-font-style:italic}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}",{"title":1021,"searchDepth":1059,"depth":1059,"links":4233},[4234,4235,4236,4237,4248,4251,4252,4253,4254,4255],{"id":902,"depth":1059,"text":903},{"id":1013,"depth":1059,"text":1014},{"id":1385,"depth":1059,"text":1386},{"id":1578,"depth":1059,"text":1579,"children":4238},[4239,4240,4241,4242,4243,4244,4245,4246,4247],{"id":1583,"depth":1065,"text":1584},{"id":1817,"depth":1065,"text":1818},{"id":1985,"depth":1065,"text":1986},{"id":2128,"depth":1065,"text":2129},{"id":2269,"depth":1065,"text":2270},{"id":2375,"depth":1065,"text":2376},{"id":2524,"depth":1065,"text":2525},{"id":2698,"depth":1065,"text":2699},{"id":3067,"depth":1065,"text":3068},{"id":3428,"depth":1059,"text":3429,"children":4249},[4250],{"id":3595,"depth":1065,"text":1327},{"id":3694,"depth":1059,"text":3695},{"id":3766,"depth":1059,"text":3654},{"id":3810,"depth":1059,"text":3811},{"id":3916,"depth":1059,"text":3917},{"id":4087,"depth":1059,"text":4088},"How the IAM service inspects every refresh-token use for behavioral anomalies, which checks it runs, and when each one triggers adaptive MFA or a hard block.","md","i-lucide-scan-eye",{},null,"---\ntitle: Anomaly Detection\ndescription: How the IAM service inspects every refresh-token use for behavioral anomalies, which checks it runs, and when each one triggers adaptive MFA or a hard block.\nicon: i-lucide-scan-eye\n---\n\nEvery time a refresh token is used, the IAM service runs `strangeThings()` to decide whether the request is legitimate, suspicious, or hostile. The function queries the `refresh_tokens`, `users`, and `visitors` tables in a single lookup, then runs nine sequential checks against the results. The first check that fails short-circuits the rest and returns immediately.\n\nEach failure carries a `reqMFA` flag. When `reqMFA` is `true`, the caller sends an email OTP challenge and the user can recover by proving their identity. When `reqMFA` is `false`, the request is a hard block: the token is revoked and the user must log in again.\n\n---\n\n## Routes\n\n`strangeThings` is called from two places in the middleware chain, each with a different value for the `rotated` parameter.\n\n| Caller | Route | `rotated` | Purpose |\n|---|---|---|---|\n| `protectRoute` | Every authenticated request | `false` | Routine anomaly check during access-token verification |\n| `rotateCredentials` | `POST \u002Fauth\u002Fuser\u002Frefresh-session` | `true` | Rotation-time check with reuse detection enabled |\n\nWhen `rotated` is `true`, the function adds an extra condition: if the token has already been consumed (`usage_count > 0`), the request is hard-blocked. This catches replay attacks during the rotation window. When `rotated` is `false`, the function skips the `usage_count` check because `consumeAndVerifyRefreshToken` handles reuse detection separately downstream.\n\nSee [Refresh Tokens](\u002Fdocs\u002Fiam\u002Fessentials\u002Frefresh-tokens) for the full rotation and consumption flow.\n\n---\n\n## Function signature\n\n```ts\nimport { strangeThings } from '@riavzon\u002Fauth'\n\nconst { valid, reason, reqMFA, userId, visitorId } = await strangeThings(\n  rawRefreshToken,          \u002F\u002F Raw token string from the client cookie\n  canary_id,                \u002F\u002F canary_id cookie value\n  req.ip!,                  \u002F\u002F Client IP address\n  req.get('User-Agent')!,   \u002F\u002F Raw User-Agent header\n  false                     \u002F\u002F true when called from the rotation controller\n)\n\nif (!valid && reqMFA) {\n  \u002F\u002F Issue adaptive MFA email — see MFA docs\n} else if (!valid) {\n  \u002F\u002F Hard block — revoke session and return 401\n}\n```\n\n| Parameter | Type | Description |\n|---|---|---|\n| `token` | `string` | The raw (unhashed) refresh token from the `session` cookie |\n| `cookie` | `string` | The `canary_id` cookie value. See [canary_id lifecycle](#canary-id-lifecycle) |\n| `ipAddress` | `string` | Client IP from `req.ip` |\n| `ua` | `string` | Raw `User-Agent` header string |\n| `rotated` | `boolean` | `true` when the token has already been rotated this request |\n\nThe function hashes the token with SHA-256 before querying the database. No plaintext token ever reaches the database layer.\n\n---\n\n## The data lookup\n\nBefore any checks run, `strangeThings` check the local LRU cache from `anomaliesCache`. if the cache exists and it marked as unresolved anomaly it short circuit and returns the cache results from the previous run.\n\nWhen no cache exists, or it marked as resolved, `strangeThings` joins three tables in a single query:\n\n- **`refresh_tokens`** provides the token's validity, expiry, creation time, and `usage_count`\n- **`users`** provides the `visitor_id` foreign key and the `last_mfa_at` timestamp\n- **`visitors`** provides the stored fingerprint (IP, geo, User-Agent fields, device metadata, proxy\u002Fhosting flags, and `suspicious_activity_score`)\n\nThe join key chain is: `refresh_tokens.user_id` to `users.id` to `users.visitor_id` to `visitors.visitor_id`. If no row is found, the function returns `{ valid: false, reqMFA: false }` immediately. The `visitors` table is managed by the [Bot Detector](\u002Fdocs\u002Fbot-detection) middleware and is described in [Device Fingerprinting](#device-fingerprinting-and-the-bot-detector).\n\nThe cache is a LRU instance available for library users via `anomaliesCache`, and its keyed by a hashed refresh token.\n\nThe cache body:\n\n```ts\ninterface AnomaliesCache {\n    anomalyType: string,\n    canaryCookie: string\n    visitorId?: string\n    userId?: number\n    resolved: boolean;\n    resolvable: boolean;\n}\n```\nAn entry is deleted when a user complete successfully a OTP challenge send to its email, or when the callers fails sending this email.\n\n---\n\n## The nine checks\n\n### 1. Token validity and reuse\n\nThe function inspects the token row from the database. If the token does not exist, has been revoked (`valid = false`), or has already been consumed while `rotated` is `true`, the token is revoked and the request is blocked.\n\n| `valid` | `reqMFA` | Reason |\n|---------|----------|--------|\n| `false` | `false` | Token not found, already invalid, or replayed |\n\nThe revocation call ensures the token row is marked `valid = 0` even if the row was still technically valid but replayed. This prevents the same token from being retried.\n\n::warning\nWhen a replayed token is detected during rotation (`rotated = true` and `usage_count > 0`), the service revokes the session. The separate `consumeAndVerifyRefreshToken` path handles mass revocation (revoking **all** sessions for the user) when a fully consumed token is presented a second time. Together, these two detection points make replay attacks observable from both the rotation controller and the consumption path. See [Reuse Detection](\u002Fdocs\u002Fiam\u002Fessentials\u002Frefresh-tokens#reuse-detection) for the full scenario walkthrough.\n::\n\nCache:\n\n```ts\ncache.set(hashedClientToken, {\n     anomalyType: 'No token found',\n     canaryCookie: cookie,\n     resolved: false,\n     resolvable: false,\n})\n```\n\n```ts\ncache.set(hashedClientToken, {\n  anomalyType: 'token is invalid or being used more then ones',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: false,\n})\n```\n\n### 2. Canary cookie mismatch\n\nThe stored `canary_id` in the `visitors` table is compared against the `canary_id` cookie sent with the request. A mismatch means the request is arriving from a different browser or device than the one that established the session. MFA is required so the user can verify their identity from the new context.\n\n| `valid` | `reqMFA` | Reason |\n|---------|----------|--------|\n| `false` | `true` | `new device` |\n\nThe `canary_id` is a cryptographically random 64-character hex string set as an `httpOnly`, `Secure`, `SameSite=Lax` cookie with a 90-day TTL. It is generated by the [Bot Detector middleware](\u002Fdocs\u002Fbot-detection\u002Fgetting-started) on first contact and used as the primary key in the `visitors` table. See [canary_id lifecycle](#canary-id-lifecycle) for how it flows through login and anomaly detection.\n\nCache:\n\n```ts\ncache.set(hashedClientToken, {\n  anomalyType: 'new device',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: user_id,\n  visitorId: visitor_id,\n})\n```\n\n### 3. Idle detection\n\nIf the visitor's `last_seen` timestamp is older than 24 hours, the session is flagged as idle. The assumption is that a legitimate user returning after a long gap should re-verify. MFA is triggered.\n\n| `valid` | `reqMFA` | Reason |\n|---------|----------|--------|\n| `false` | `true` | `idle` |\n\nThe `last_seen` column in the `visitors` table is updated automatically by the Bot Detector on every request. It reflects the last time any request (not just auth requests) was seen from that `canary_id`.\n\nCache:\n\n```ts\ncache.set(hashedClientToken, {\n  anomalyType: 'idle',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: user_id,\n  visitorId: visitor_id,\n})\n```\n\n### 4. Session count limit\n\nThe function counts all valid (non-expired, non-revoked) refresh tokens for the user. If the count equals or exceeds `maxAllowedSessionsPerUser`, MFA is required. This prevents an attacker from silently opening many sessions.\n\n| `valid` | `reqMFA` | Reason |\n|---------|----------|--------|\n| `false` | `true` | `more than N active sessions` |\n\nThis check respects the `byPassAnomaliesFor` cooldown. If the user completed MFA within the configured window, the session count check is skipped entirely. See [MFA Bypass Window](#mfa-bypass-window).\n\nCache:\n\n```ts\ncache.set(hashedClientToken, {\n  anomalyType: `more than X active sessions`,\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: user_id,\n  visitorId: visitor_id,\n})\n```\n\n### 5. Rapid token creation\n\nIf the user has created more than three valid tokens in the last 10 minutes, the current token is revoked immediately. This pattern indicates an automated attack such as a race condition exploit or a token-farming script. No MFA is offered because the speed of creation suggests a non-human actor.\n\n| `valid` | `reqMFA` | Reason |\n|---------|----------|--------|\n| `false` | `false` | `3 tokens in less than 10 min` |\n\nCache:\n\n```ts\ncache.set(hashedClientToken, {\n  anomalyType: `3 tokens in less than 10 min`,\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: false,\n})\n```\n\n### 6. IP range mismatch\n\nThe incoming IP address is compared against the stored `ip_address` in the `visitors` table using the `ip-range-check` library. If the current IP does not fall within the same range as the stored address, MFA is triggered. This catches IP changes caused by VPN switches or network hops while allowing normal ISP rotation within the same subnet.\n\n| `valid` | `reqMFA` | Reason |\n|---------|----------|--------|\n| `false` | `true` | `Ip does not match` |\n\nCache:\n\n```ts\ncache.set(hashedClientToken, {\n  anomalyType: 'Ip does not match',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n```\n\n### 7. Suspicious activity score\n\nEvery visitor accumulates a `suspicious_activity_score` through the [Bot Detector scoring pipeline](\u002Fdocs\u002Fbot-detection\u002Fguides\u002FSCORE). The anomaly engine triggers MFA when the score reaches or exceeds **25%** of the configured `banScore`. The 25% threshold is intentional: MFA challenges start well before the visitor reaches the ban threshold, giving legitimate users a chance to prove themselves while slowing down attackers.\n\n| `valid` | `reqMFA` | Reason |\n|---------|----------|--------|\n| `false` | `true` | `Suspicion score to high` |\n\nThe `banScore` defaults to `100` and is configurable through the `botDetector.settings.banScore` option. At the default, MFA triggers at a score of `25`. See [Bot Detector Configuration](\u002Fdocs\u002Fbot-detection\u002Fconfiguration) for the full scoring options.\n\nCache:\n\n```ts\ncache.set(hashedClientToken, {\n  anomalyType: 'Suspicion score to high',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n```\n\n### 8. Proxy and hosting detection\n\nThe incoming IP is geolocated by calling `getGeoData()` from the [Bot Detector](\u002Fdocs\u002Fbot-detection). This function queries the local MMDB databases and returns geo information along with `proxy` and `hosting` boolean flags. The `hosting` flag is `true` when the ASN classification is `\"Content\"` or when the IP belongs to a known Tor exit node.\n\nIf the request arrives through a proxy or hosting provider, the service checks the visitor's `proxy_allowed` and `hosting_allowed` flags in the `visitors` table.\n\n| Incoming | Stored flag | `valid` | `reqMFA` |\n|----------|-------------|---------|----------|\n| Proxy | `proxy_allowed: true` | `true` | `false` |\n| Proxy | `proxy_allowed: false` | `false` | `true` |\n| Hosting | `hosting_allowed: true` | `true` | `false` |\n| Hosting | `hosting_allowed: false` | `false` | `true` |\n\n::warning\nWhen a proxy or hosting request is allowed (flags are `true`), the function returns `valid: true` immediately and **skips the fingerprint consistency loop** (check 9). This is by design: proxy and hosting environments often rotate IPs and geo data frequently, which would cause false positives in the fingerprint comparison. Once a user has been MFA-verified on a proxy connection, subsequent requests from any proxy are trusted without field-by-field comparison.\n::\n\nBoth flags default to `false` in the `visitors` table. They are set to `true` during [MFA verification](\u002Fdocs\u002Fiam\u002Fessentials\u002Fmfa). When a user completes an MFA challenge that was triggered by proxy or hosting detection, the `proxy_allowed` and `hosting_allowed` columns are both updated to `true` on the visitor record. This means MFA is a one-time gate per device for proxy\u002Fhosting users.\n\nCache:\n\n```ts\ncache.set(hashedClientToken, {\n  anomalyType: 'Proxy Or hosting',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n```\n\n```ts\ncache.set(hashedClientToken, {\n  anomalyType: 'Proxy or hosting allowed',\n  canaryCookie: cookie,\n  resolved: true,\n  resolvable: false,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n```\n\n### 9. Device fingerprint consistency loop\n\nThe final check runs only when the request is not coming through a proxy or hosting provider (or when the user's flags have not been set yet). The function builds a fingerprint object by merging the incoming geo data (from `getGeoData`) with the parsed User-Agent fields (from `parseUA`) and iterates over every key in the result. For each key that also exists in the stored visitor record, it compares the values.\n\n**The comparison skips a field if either side is `null`, `undefined`, or the string `'unknown'`.** This prevents false positives on fields that were not populated during the initial fingerprint capture.\n\nCompared fields include:\n\n| Category | Fields |\n|----------|--------|\n| Geo | `country`, `city`, `district`, `lat`, `lon`, `timezone`, `currency`, `isp`, `org`, `as_org` |\n| Device | `device`, `deviceVendor`, `deviceModel` |\n| Browser | `browser`, `browserType`, `browserVersion`, `os` |\n\nAny single mismatch on a populated field triggers MFA.\n\n| `valid` | `reqMFA` | Reason |\n|---------|----------|--------|\n| `false` | `true` | `Loop detected` |\n\nCache:\n\n```ts\ncache.set(hashedClientToken, {\n  anomalyType: 'Loop detected',\n  canaryCookie: cookie,\n  resolved: false,\n  resolvable: true,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n```\n\nIf all nine checks pass, the function returns `{ valid: true, reason: 'Checks passed', reqMFA: false }`,\n\nand sets: \n\n```ts\ncache.set(hashedClientToken, {\n  anomalyType: 'Checks passed',\n  canaryCookie: cookie,\n  resolved: true,\n  resolvable: false,\n  userId: tokenResults.user_id,\n  visitorId: tokenResults.visitor_id,\n})\n```\n\n---\n\n## Device fingerprinting and the Bot Detector\n\nThe anomaly engine relies on the `visitors` table, which is created and managed by the [Bot Detector](\u002Fdocs\u002Fbot-detection\u002Fgetting-started) module. The Bot Detector runs as Express middleware (`detectBots`) before any auth routes and is responsible for:\n\n1. Generating the `canary_id` cookie on first contact\n2. Resolving geo data from the client IP via local MMDB databases\n3. Parsing the User-Agent header into structured device, browser, and OS fields\n4. Upserting the visitor record with all fingerprint fields\n5. Accumulating a `suspicious_activity_score` through the [checker pipeline](\u002Fdocs\u002Fbot-detection\u002Fcheckers)\n\nThe auth service imports three helpers directly from `@riavzon\u002Fbot-detector`:\n\n| Helper | Purpose | Used by |\n|---|---|---|\n| `getGeoData(ip)` | Returns a `GeoResponse` object with geo fields, `proxy`, and `hosting` booleans | `strangeThings` (checks 8 and 9), `getFingerPrint` middleware |\n| `parseUA(ua)` | Returns a `ParsedUAResult` with `device`, `browser`, `os`, `bot`, `botAI`, and vendor info | `strangeThings` (check 9), `getFingerPrint` middleware |\n| `updateVisitors(data, cookie, visitorId)` | Updates the visitor row with fresh fingerprint fields | `trustVisitor` (called during login) |\n\nThe `getFingerPrint` middleware in the auth service calls both `getGeoData` and `parseUA` and attaches the merged result to `req.fingerPrint`. This middleware runs in the rotation and MFA routes so that the caller has access to the current fingerprint data. The anomaly engine itself calls `getGeoData` and `parseUA` independently for checks 8 and 9 rather than reading from `req.fingerPrint`, because `strangeThings` receives the raw IP and User-Agent as parameters and does its own resolution.\n\n### canary_id lifecycle\n\nThe `canary_id` cookie is the thread that ties a browser session to a visitor record across authentication events. Here is how it flows:\n\n::steps{level=\"4\"}\n#### First visit\n\nThe Bot Detector middleware generates a 64-character hex string using `randomBytes(32)` and sets it as the `canary_id` cookie. A visitor row is inserted with this value as the primary key, along with the initial geo and UA fingerprint.\n\n#### Login\n\nThe login controller reads the `canary_id` from `req.cookies`. If `trustUserDeviceOnAuth` is enabled and the bot-detector assigned a new `visitor_id` for this request (`req.newVisitorId`), the controller calls `trustVisitor()` to update the user's `visitor_id` foreign key to point at the new visitor row. This re-baselines the fingerprint so that subsequent anomaly checks compare against the device the user actually logged in from. See [Trusted devices on login](#trusted-devices-on-login).\n\n#### Token rotation\n\nThe `rotateCredentials` controller passes the `canary_id` cookie to `strangeThings()` where it is compared against the stored value (check 2). If it matches, the rest of the checks proceed. If it does not match, MFA is triggered.\n\n#### MFA completion\n\nAfter a successful MFA challenge, the service updates `last_mfa_at` on the user row and sets `proxy_allowed` and `hosting_allowed` to `true` on the visitor record. This allows subsequent proxy\u002Fhosting requests from the same `canary_id` to pass without further MFA.\n::\n\n---\n\n## MFA bypass window\n\nWhen a user completes MFA, the `last_mfa_at` timestamp is updated in the `users` table. For the duration of `byPassAnomaliesFor` milliseconds after that timestamp, check 4 (session count limit) is skipped. This prevents a user from being MFA-challenged again immediately after verifying a new device or session.\n\n```ts\njwt: {\n  refresh_tokens: {\n    byPassAnomaliesFor: 300_000   \u002F\u002F 5 minutes\n  }\n}\n```\n\nThe bypass only applies to the session count check. All other checks still run regardless of when MFA was last completed.\n\n::tip\nWhen a refresh token expires, the verification functions set `last_mfa_at` to `NULL` on the user row. This resets the `byPassAnomaliesFor` cooldown window. The next time the user hits any anomaly condition that checks the bypass, it will not be skipped. A clean login from the same device with no anomalies still passes without MFA.\n::\n\n---\n\n## Trusted devices on login\n\nThe `trustUserDeviceOnAuth` configuration option controls whether the login controller re-baselines the user's visitor record. When set to `true`, the login flow calls `trustVisitor()` after a successful password check. This function:\n\n1. Updates `users.visitor_id` to point at the visitor row associated with the current `canary_id`\n2. Calls `updateVisitors()` from the [Bot Detector](\u002Fdocs\u002Fbot-detection) to overwrite the visitor fingerprint with the current request's geo and UA data\n\nThe effect is that the stored fingerprint always reflects the device the user most recently logged in from. Without this option, the fingerprint stays fixed to whatever device was first associated with the user, which can cause false positives when users legitimately switch devices.\n\nThe same logic runs in the [OAuth](\u002Fdocs\u002Fiam\u002Fessentials\u002Foauth) flow after a successful external provider callback.\n\n---\n\n## Return value\n\n```ts\ninterface AnomalyResult {\n  valid: boolean\n  reason: string        \u002F\u002F Human-readable description of the outcome\n  reqMFA: boolean       \u002F\u002F true = issue MFA; false = hard block or pass\n  userId?: number       \u002F\u002F Present when reqMFA is true\n  visitorId?: string    \u002F\u002F Present when reqMFA is true\n}\n```\n\nWhen `reqMFA` is `true`, `userId` and `visitorId` are included so the caller can construct and send the MFA email without an additional database lookup. See [MFA](\u002Fdocs\u002Fiam\u002Fessentials\u002Fmfa) for the full challenge and verification flow.\n\nWhen `reqMFA` is `false` and `valid` is `false`, the session has been hard-blocked. The token is already revoked and the client must re-authenticate from scratch.\n\n---\n\n## Decision summary\n\n| Check | Trigger | `reqMFA` | Rationale |\n|---|---|---|---|\n| 1. Token validity | Token missing, revoked, or replayed | `false` | No identity to challenge. Session is dead. |\n| 2. Canary mismatch | Different `canary_id` cookie | `true` | New device. User can prove identity. |\n| 3. Idle | `last_seen` older than 24 hours | `true` | Stale session. Re-verify before continuing. |\n| 4. Session count | `>= maxAllowedSessionsPerUser` | `true` | Too many devices. User picks which to keep. |\n| 5. Rapid creation | More than 3 tokens in 10 minutes | `false` | Automated behavior. Hard block. |\n| 6. IP range | IP outside stored range | `true` | Network change. User can confirm. |\n| 7. Suspicion score | Score `>= banScore * 0.25` | `true` | Bot-like behavior building up. Early challenge. |\n| 8. Proxy\u002Fhosting | Proxy or hosting without allow flag | `true` | New infrastructure. One-time MFA gate. |\n| 9. Fingerprint | Any geo or UA field mismatch | `true` | Environment changed. User can re-verify. |\n\n---\n\n## Configuration reference\n\nAll options that affect anomaly detection:\n\n| Option | Location | Type | Default | Description |\n|---|---|---|---|---|\n| `maxAllowedSessionsPerUser` | `jwt.refresh_tokens` | `number` | — | Maximum concurrent valid refresh tokens before check 4 triggers |\n| `byPassAnomaliesFor` | `jwt.refresh_tokens` | `number` | — | Milliseconds after `last_mfa_at` during which session count check is skipped |\n| `enableBotDetector` | `botDetector` | `boolean` | — | Enables or disables the Bot Detector integration entirely |\n| `banScore` | `botDetector.settings` | `number` | `100` | Score threshold for banning a visitor. MFA triggers at 25% of this value |\n| `trustUserDeviceOnAuth` | Root config | `boolean` | `false` | Whether login re-baselines the visitor fingerprint to the current device |\n\nSee [Configuration](\u002Fdocs\u002Fiam\u002Fconfiguration) for the full schema reference and [Bot Detector Configuration](\u002Fdocs\u002Fbot-detection\u002Fconfiguration) for the scoring and checker pipeline options.\n",{"title":95,"description":4256},"dbau7I-uqj-DyrsFbKy6JbKitZ2wjZ9WpD6KxKdsLWQ",[4265,4266],{"title":91,"path":92,"stem":93,"children":-1},{"title":99,"path":100,"stem":101,"children":-1},{"id":851,"title":95,"body":4268,"description":4256,"extension":4257,"icon":4258,"meta":7052,"module":4260,"navigation":8,"path":96,"rawbody":4261,"seo":7053,"stem":97,"__hash__":4263},{"type":853,"value":4269,"toc":7028},[4270,4280,4292,4294,4296,4302,4350,4366,4370,4372,4374,4546,4632,4634,4636,4638,4644,4648,4676,4694,4698,4700,4770,4772,4774,4776,4778,4786,4816,4820,4834,4836,4902,4968,4970,4978,5010,5026,5028,5114,5116,5120,5152,5160,5162,5248,5250,5254,5286,5292,5294,5376,5378,5380,5412,5414,5476,5478,5486,5518,5520,5614,5616,5626,5658,5670,5672,5766,5768,5784,5792,5876,5886,5902,5904,5998,6092,6094,6100,6110,6112,6176,6178,6210,6212,6306,6310,6312,6406,6408,6410,6418,6436,6440,6512,6530,6532,6536,6590,6592,6594,6602,6630,6632,6642,6644,6646,6654,6668,6670,6674,6676,6678,6742,6754,6764,6766,6768,6902,6904,6906,6908,7020,7026],[856,4271,858,4272,863,4274,867,4276,871,4278,875],{},[860,4273,862],{},[860,4275,866],{},[860,4277,870],{},[860,4279,874],{},[856,4281,878,4282,882,4284,885,4286,889,4288,885,4290,895],{},[860,4283,881],{},[860,4285,881],{},[860,4287,888],{},[860,4289,881],{},[860,4291,894],{},[897,4293],{},[900,4295,903],{"id":902},[856,4297,4298,909,4300,913],{},[860,4299,908],{},[860,4301,912],{},[915,4303,4304,4318],{},[918,4305,4306],{},[921,4307,4308,4310,4312,4316],{},[924,4309,926],{},[924,4311,929],{},[924,4313,4314],{},[860,4315,912],{},[924,4317,936],{},[938,4319,4320,4334],{},[921,4321,4322,4326,4328,4332],{},[943,4323,4324],{},[860,4325,947],{},[943,4327,950],{},[943,4329,4330],{},[860,4331,894],{},[943,4333,957],{},[921,4335,4336,4340,4344,4348],{},[943,4337,4338],{},[860,4339,964],{},[943,4341,4342],{},[860,4343,969],{},[943,4345,4346],{},[860,4347,888],{},[943,4349,976],{},[856,4351,979,4352,885,4354,984,4356,988,4358,885,4360,993,4362,997,4364,1001],{},[860,4353,912],{},[860,4355,888],{},[860,4357,987],{},[860,4359,912],{},[860,4361,894],{},[860,4363,996],{},[860,4365,1000],{},[856,4367,1004,4368,1008],{},[1006,4369,91],{"href":92},[897,4371],{},[900,4373,1014],{"id":1013},[1016,4375,4376],{"className":1018,"code":1019,"language":1020,"meta":1021,"style":1021},[860,4377,4378,4396,4400,4434,4442,4450,4464,4488,4494,4498,4502,4518,4522,4538,4542],{"__ignoreMap":1021},[1025,4379,4380,4382,4384,4386,4388,4390,4392,4394],{"class":1027,"line":1028},[1025,4381,1032],{"class":1031},[1025,4383,1036],{"class":1035},[1025,4385,908],{"class":1039},[1025,4387,1042],{"class":1035},[1025,4389,1045],{"class":1031},[1025,4391,1049],{"class":1048},[1025,4393,1053],{"class":1052},[1025,4395,1056],{"class":1048},[1025,4397,4398],{"class":1027,"line":1059},[1025,4399,1062],{"emptyLinePlaceholder":8},[1025,4401,4402,4404,4406,4408,4410,4412,4414,4416,4418,4420,4422,4424,4426,4428,4430,4432],{"class":1027,"line":1065},[1025,4403,1069],{"class":1068},[1025,4405,1036],{"class":1035},[1025,4407,1075],{"class":1074},[1025,4409,867],{"class":1035},[1025,4411,1080],{"class":1074},[1025,4413,867],{"class":1035},[1025,4415,881],{"class":1074},[1025,4417,867],{"class":1035},[1025,4419,1089],{"class":1074},[1025,4421,867],{"class":1035},[1025,4423,1094],{"class":1074},[1025,4425,1042],{"class":1035},[1025,4427,1100],{"class":1099},[1025,4429,1103],{"class":1031},[1025,4431,1107],{"class":1106},[1025,4433,1110],{"class":1035},[1025,4435,4436,4438,4440],{"class":1027,"line":1113},[1025,4437,1116],{"class":1039},[1025,4439,1119],{"class":1035},[1025,4441,1123],{"class":1122},[1025,4443,4444,4446,4448],{"class":1027,"line":1126},[1025,4445,1129],{"class":1039},[1025,4447,1132],{"class":1035},[1025,4449,1135],{"class":1122},[1025,4451,4452,4454,4456,4458,4460,4462],{"class":1027,"line":1138},[1025,4453,1141],{"class":1039},[1025,4455,1144],{"class":1035},[1025,4457,1147],{"class":1039},[1025,4459,1150],{"class":1099},[1025,4461,1153],{"class":1035},[1025,4463,1156],{"class":1122},[1025,4465,4466,4468,4470,4472,4474,4476,4478,4480,4482,4484,4486],{"class":1027,"line":1159},[1025,4467,1141],{"class":1039},[1025,4469,1144],{"class":1035},[1025,4471,1166],{"class":1106},[1025,4473,1169],{"class":1035},[1025,4475,1172],{"class":1048},[1025,4477,1175],{"class":1052},[1025,4479,1172],{"class":1048},[1025,4481,1180],{"class":1035},[1025,4483,1150],{"class":1099},[1025,4485,1185],{"class":1035},[1025,4487,1188],{"class":1122},[1025,4489,4490,4492],{"class":1027,"line":1191},[1025,4491,1195],{"class":1194},[1025,4493,1198],{"class":1122},[1025,4495,4496],{"class":1027,"line":1201},[1025,4497,1204],{"class":1035},[1025,4499,4500],{"class":1027,"line":1207},[1025,4501,1062],{"emptyLinePlaceholder":8},[1025,4503,4504,4506,4508,4510,4512,4514,4516],{"class":1027,"line":1212},[1025,4505,1215],{"class":1031},[1025,4507,1218],{"class":1035},[1025,4509,1150],{"class":1099},[1025,4511,1075],{"class":1039},[1025,4513,1225],{"class":1099},[1025,4515,1228],{"class":1039},[1025,4517,1231],{"class":1035},[1025,4519,4520],{"class":1027,"line":1234},[1025,4521,1237],{"class":1122},[1025,4523,4524,4526,4528,4530,4532,4534,4536],{"class":1027,"line":1240},[1025,4525,1243],{"class":1035},[1025,4527,1246],{"class":1031},[1025,4529,1249],{"class":1031},[1025,4531,1218],{"class":1035},[1025,4533,1150],{"class":1099},[1025,4535,1075],{"class":1039},[1025,4537,1231],{"class":1035},[1025,4539,4540],{"class":1027,"line":1260},[1025,4541,1263],{"class":1122},[1025,4543,4544],{"class":1027,"line":1266},[1025,4545,1269],{"class":1035},[915,4547,4548,4558],{},[918,4549,4550],{},[921,4551,4552,4554,4556],{},[924,4553,1278],{},[924,4555,1281],{},[924,4557,1284],{},[938,4559,4560,4574,4590,4604,4618],{},[921,4561,4562,4566,4570],{},[943,4563,4564],{},[860,4565,1293],{},[943,4567,4568],{},[860,4569,1298],{},[943,4571,1301,4572,1305],{},[860,4573,1304],{},[921,4575,4576,4580,4584],{},[943,4577,4578],{},[860,4579,1312],{},[943,4581,4582],{},[860,4583,1298],{},[943,4585,1319,4586,1323,4588],{},[860,4587,1322],{},[1006,4589,1327],{"href":1326},[921,4591,4592,4596,4600],{},[943,4593,4594],{},[860,4595,1334],{},[943,4597,4598],{},[860,4599,1298],{},[943,4601,1341,4602],{},[860,4603,1344],{},[921,4605,4606,4610,4614],{},[943,4607,4608],{},[860,4609,1351],{},[943,4611,4612],{},[860,4613,1298],{},[943,4615,1358,4616,1361],{},[860,4617,1175],{},[921,4619,4620,4624,4628],{},[943,4621,4622],{},[860,4623,912],{},[943,4625,4626],{},[860,4627,1372],{},[943,4629,4630,1377],{},[860,4631,888],{},[856,4633,1380],{},[897,4635],{},[900,4637,1386],{"id":1385},[856,4639,1389,4640,1392,4642,1396],{},[860,4641,908],{},[860,4643,1395],{},[856,4645,1399,4646,1402],{},[860,4647,908],{},[1404,4649,4650,4658,4668],{},[1407,4651,4652,1414,4656],{},[1410,4653,4654],{},[860,4655,866],{},[860,4657,996],{},[1407,4659,4660,1423,4664,1427,4666,1431],{},[1410,4661,4662],{},[860,4663,870],{},[860,4665,1426],{},[860,4667,1430],{},[1407,4669,4670,1438,4674,1180],{},[1410,4671,4672],{},[860,4673,874],{},[860,4675,1441],{},[856,4677,1444,4678,1448,4680,1448,4682,1448,4684,1458,4686,1462,4688,1465,4690,1468,4692,1144],{},[860,4679,1447],{},[860,4681,1451],{},[860,4683,1454],{},[860,4685,1457],{},[860,4687,1461],{},[860,4689,874],{},[1006,4691,399],{"href":35},[1006,4693,1472],{"href":1471},[856,4695,1475,4696,1478],{},[860,4697,1395],{},[856,4699,1481],{},[1016,4701,4702],{"className":1018,"code":1484,"language":1020,"meta":1021,"style":1021},[860,4703,4704,4712,4722,4730,4738,4746,4756,4766],{"__ignoreMap":1021},[1025,4705,4706,4708,4710],{"class":1027,"line":1028},[1025,4707,1491],{"class":1068},[1025,4709,1495],{"class":1494},[1025,4711,1498],{"class":1035},[1025,4713,4714,4716,4718,4720],{"class":1027,"line":1059},[1025,4715,1503],{"class":1039},[1025,4717,1506],{"class":1099},[1025,4719,1509],{"class":1494},[1025,4721,1512],{"class":1035},[1025,4723,4724,4726,4728],{"class":1027,"line":1065},[1025,4725,1517],{"class":1039},[1025,4727,1506],{"class":1099},[1025,4729,1522],{"class":1494},[1025,4731,4732,4734,4736],{"class":1027,"line":1113},[1025,4733,1527],{"class":1039},[1025,4735,1530],{"class":1099},[1025,4737,1522],{"class":1494},[1025,4739,4740,4742,4744],{"class":1027,"line":1126},[1025,4741,1537],{"class":1039},[1025,4743,1530],{"class":1099},[1025,4745,1542],{"class":1494},[1025,4747,4748,4750,4752,4754],{"class":1027,"line":1138},[1025,4749,1547],{"class":1039},[1025,4751,1506],{"class":1099},[1025,4753,1552],{"class":1494},[1025,4755,1555],{"class":1035},[1025,4757,4758,4760,4762,4764],{"class":1027,"line":1159},[1025,4759,1560],{"class":1039},[1025,4761,1506],{"class":1099},[1025,4763,1552],{"class":1494},[1025,4765,1555],{"class":1035},[1025,4767,4768],{"class":1027,"line":1191},[1025,4769,1269],{"class":1035},[856,4771,1573],{},[897,4773],{},[900,4775,1579],{"id":1578},[1581,4777,1584],{"id":1583},[856,4779,1587,4780,1591,4782,885,4784,1596],{},[860,4781,1590],{},[860,4783,912],{},[860,4785,888],{},[915,4787,4788,4802],{},[918,4789,4790],{},[921,4791,4792,4796,4800],{},[924,4793,4794],{},[860,4795,1075],{},[924,4797,4798],{},[860,4799,881],{},[924,4801,1613],{},[938,4803,4804],{},[921,4805,4806,4810,4814],{},[943,4807,4808],{},[860,4809,894],{},[943,4811,4812],{},[860,4813,894],{},[943,4815,1628],{},[856,4817,1631,4818,1635],{},[860,4819,1634],{},[1637,4821,4822],{},[856,4823,1641,4824,1645,4826,1648,4828,1651,4830,1655,4832,1660],{},[860,4825,1644],{},[860,4827,987],{},[860,4829,1000],{},[1410,4831,1654],{},[1006,4833,1659],{"href":1658},[856,4835,1663],{},[1016,4837,4838],{"className":1018,"code":1666,"language":1020,"meta":1021,"style":1021},[860,4839,4840,4854,4868,4878,4888,4898],{"__ignoreMap":1021},[1025,4841,4842,4844,4846,4848,4850,4852],{"class":1027,"line":1028},[1025,4843,1673],{"class":1039},[1025,4845,1144],{"class":1035},[1025,4847,1678],{"class":1106},[1025,4849,1169],{"class":1035},[1025,4851,1683],{"class":1039},[1025,4853,1686],{"class":1035},[1025,4855,4856,4858,4860,4862,4864,4866],{"class":1027,"line":1059},[1025,4857,1691],{"class":1039},[1025,4859,1506],{"class":1694},[1025,4861,1049],{"class":1048},[1025,4863,1699],{"class":1052},[1025,4865,1172],{"class":1048},[1025,4867,1512],{"class":1035},[1025,4869,4870,4872,4874,4876],{"class":1027,"line":1065},[1025,4871,1708],{"class":1039},[1025,4873,1506],{"class":1694},[1025,4875,1305],{"class":1039},[1025,4877,1512],{"class":1035},[1025,4879,4880,4882,4884,4886],{"class":1027,"line":1113},[1025,4881,1719],{"class":1039},[1025,4883,1506],{"class":1694},[1025,4885,1724],{"class":1194},[1025,4887,1512],{"class":1035},[1025,4889,4890,4892,4894,4896],{"class":1027,"line":1126},[1025,4891,1731],{"class":1039},[1025,4893,1506],{"class":1694},[1025,4895,1724],{"class":1194},[1025,4897,1512],{"class":1035},[1025,4899,4900],{"class":1027,"line":1138},[1025,4901,1742],{"class":1035},[1016,4903,4904],{"className":1018,"code":1745,"language":1020,"meta":1021,"style":1021},[860,4905,4906,4920,4934,4944,4954,4964],{"__ignoreMap":1021},[1025,4907,4908,4910,4912,4914,4916,4918],{"class":1027,"line":1028},[1025,4909,1673],{"class":1039},[1025,4911,1144],{"class":1035},[1025,4913,1678],{"class":1106},[1025,4915,1169],{"class":1035},[1025,4917,1683],{"class":1039},[1025,4919,1686],{"class":1035},[1025,4921,4922,4924,4926,4928,4930,4932],{"class":1027,"line":1059},[1025,4923,1766],{"class":1039},[1025,4925,1506],{"class":1694},[1025,4927,1049],{"class":1048},[1025,4929,1773],{"class":1052},[1025,4931,1172],{"class":1048},[1025,4933,1512],{"class":1035},[1025,4935,4936,4938,4940,4942],{"class":1027,"line":1065},[1025,4937,1782],{"class":1039},[1025,4939,1506],{"class":1694},[1025,4941,1305],{"class":1039},[1025,4943,1512],{"class":1035},[1025,4945,4946,4948,4950,4952],{"class":1027,"line":1113},[1025,4947,1793],{"class":1039},[1025,4949,1506],{"class":1694},[1025,4951,1724],{"class":1194},[1025,4953,1512],{"class":1035},[1025,4955,4956,4958,4960,4962],{"class":1027,"line":1126},[1025,4957,1804],{"class":1039},[1025,4959,1506],{"class":1694},[1025,4961,1724],{"class":1194},[1025,4963,1512],{"class":1035},[1025,4965,4966],{"class":1027,"line":1138},[1025,4967,1742],{"class":1035},[1581,4969,1818],{"id":1817},[856,4971,1821,4972,1824,4974,1827,4976,1830],{},[860,4973,1322],{},[860,4975,874],{},[860,4977,1322],{},[915,4979,4980,4994],{},[918,4981,4982],{},[921,4983,4984,4988,4992],{},[924,4985,4986],{},[860,4987,1075],{},[924,4989,4990],{},[860,4991,881],{},[924,4993,1613],{},[938,4995,4996],{},[921,4997,4998,5002,5006],{},[943,4999,5000],{},[860,5001,894],{},[943,5003,5004],{},[860,5005,888],{},[943,5007,5008],{},[860,5009,1863],{},[856,5011,1319,5012,1868,5014,867,5016,867,5018,1878,5020,1882,5022,1885,5024,1888],{},[860,5013,1322],{},[860,5015,1871],{},[860,5017,1874],{},[860,5019,1877],{},[1006,5021,1881],{"href":404},[860,5023,874],{},[1006,5025,1327],{"href":1326},[856,5027,1663],{},[1016,5029,5030],{"className":1018,"code":1893,"language":1020,"meta":1021,"style":1021},[860,5031,5032,5046,5060,5070,5080,5090,5100,5110],{"__ignoreMap":1021},[1025,5033,5034,5036,5038,5040,5042,5044],{"class":1027,"line":1028},[1025,5035,1673],{"class":1039},[1025,5037,1144],{"class":1035},[1025,5039,1678],{"class":1106},[1025,5041,1169],{"class":1035},[1025,5043,1683],{"class":1039},[1025,5045,1686],{"class":1035},[1025,5047,5048,5050,5052,5054,5056,5058],{"class":1027,"line":1059},[1025,5049,1766],{"class":1039},[1025,5051,1506],{"class":1694},[1025,5053,1049],{"class":1048},[1025,5055,1863],{"class":1052},[1025,5057,1172],{"class":1048},[1025,5059,1512],{"class":1035},[1025,5061,5062,5064,5066,5068],{"class":1027,"line":1065},[1025,5063,1782],{"class":1039},[1025,5065,1506],{"class":1694},[1025,5067,1305],{"class":1039},[1025,5069,1512],{"class":1035},[1025,5071,5072,5074,5076,5078],{"class":1027,"line":1113},[1025,5073,1793],{"class":1039},[1025,5075,1506],{"class":1694},[1025,5077,1724],{"class":1194},[1025,5079,1512],{"class":1035},[1025,5081,5082,5084,5086,5088],{"class":1027,"line":1126},[1025,5083,1804],{"class":1039},[1025,5085,1506],{"class":1694},[1025,5087,1952],{"class":1194},[1025,5089,1512],{"class":1035},[1025,5091,5092,5094,5096,5098],{"class":1027,"line":1138},[1025,5093,1959],{"class":1039},[1025,5095,1506],{"class":1694},[1025,5097,1964],{"class":1039},[1025,5099,1512],{"class":1035},[1025,5101,5102,5104,5106,5108],{"class":1027,"line":1159},[1025,5103,1971],{"class":1039},[1025,5105,1506],{"class":1694},[1025,5107,1976],{"class":1039},[1025,5109,1512],{"class":1035},[1025,5111,5112],{"class":1027,"line":1191},[1025,5113,1742],{"class":1035},[1581,5115,1986],{"id":1985},[856,5117,1989,5118,1993],{},[860,5119,1992],{},[915,5121,5122,5136],{},[918,5123,5124],{},[921,5125,5126,5130,5134],{},[924,5127,5128],{},[860,5129,1075],{},[924,5131,5132],{},[860,5133,881],{},[924,5135,1613],{},[938,5137,5138],{},[921,5139,5140,5144,5148],{},[943,5141,5142],{},[860,5143,894],{},[943,5145,5146],{},[860,5147,888],{},[943,5149,5150],{},[860,5151,2026],{},[856,5153,1319,5154,2031,5156,2034,5158,1144],{},[860,5155,1992],{},[860,5157,874],{},[860,5159,1322],{},[856,5161,1663],{},[1016,5163,5164],{"className":1018,"code":2041,"language":1020,"meta":1021,"style":1021},[860,5165,5166,5180,5194,5204,5214,5224,5234,5244],{"__ignoreMap":1021},[1025,5167,5168,5170,5172,5174,5176,5178],{"class":1027,"line":1028},[1025,5169,1673],{"class":1039},[1025,5171,1144],{"class":1035},[1025,5173,1678],{"class":1106},[1025,5175,1169],{"class":1035},[1025,5177,1683],{"class":1039},[1025,5179,1686],{"class":1035},[1025,5181,5182,5184,5186,5188,5190,5192],{"class":1027,"line":1059},[1025,5183,1766],{"class":1039},[1025,5185,1506],{"class":1694},[1025,5187,1049],{"class":1048},[1025,5189,2026],{"class":1052},[1025,5191,1172],{"class":1048},[1025,5193,1512],{"class":1035},[1025,5195,5196,5198,5200,5202],{"class":1027,"line":1065},[1025,5197,1782],{"class":1039},[1025,5199,1506],{"class":1694},[1025,5201,1305],{"class":1039},[1025,5203,1512],{"class":1035},[1025,5205,5206,5208,5210,5212],{"class":1027,"line":1113},[1025,5207,1793],{"class":1039},[1025,5209,1506],{"class":1694},[1025,5211,1724],{"class":1194},[1025,5213,1512],{"class":1035},[1025,5215,5216,5218,5220,5222],{"class":1027,"line":1126},[1025,5217,1804],{"class":1039},[1025,5219,1506],{"class":1694},[1025,5221,1952],{"class":1194},[1025,5223,1512],{"class":1035},[1025,5225,5226,5228,5230,5232],{"class":1027,"line":1138},[1025,5227,1959],{"class":1039},[1025,5229,1506],{"class":1694},[1025,5231,1964],{"class":1039},[1025,5233,1512],{"class":1035},[1025,5235,5236,5238,5240,5242],{"class":1027,"line":1159},[1025,5237,1971],{"class":1039},[1025,5239,1506],{"class":1694},[1025,5241,1976],{"class":1039},[1025,5243,1512],{"class":1035},[1025,5245,5246],{"class":1027,"line":1191},[1025,5247,1742],{"class":1035},[1581,5249,2129],{"id":2128},[856,5251,2132,5252,2136],{},[860,5253,2135],{},[915,5255,5256,5270],{},[918,5257,5258],{},[921,5259,5260,5264,5268],{},[924,5261,5262],{},[860,5263,1075],{},[924,5265,5266],{},[860,5267,881],{},[924,5269,1613],{},[938,5271,5272],{},[921,5273,5274,5278,5282],{},[943,5275,5276],{},[860,5277,894],{},[943,5279,5280],{},[860,5281,888],{},[943,5283,5284],{},[860,5285,2169],{},[856,5287,2172,5288,2176,5290,1144],{},[860,5289,2175],{},[1006,5291,2180],{"href":2179},[856,5293,1663],{},[1016,5295,5296],{"className":1018,"code":2185,"language":1020,"meta":1021,"style":1021},[860,5297,5298,5312,5322,5332,5342,5352,5362,5372],{"__ignoreMap":1021},[1025,5299,5300,5302,5304,5306,5308,5310],{"class":1027,"line":1028},[1025,5301,1673],{"class":1039},[1025,5303,1144],{"class":1035},[1025,5305,1678],{"class":1106},[1025,5307,1169],{"class":1035},[1025,5309,1683],{"class":1039},[1025,5311,1686],{"class":1035},[1025,5313,5314,5316,5318,5320],{"class":1027,"line":1059},[1025,5315,1766],{"class":1039},[1025,5317,1506],{"class":1694},[1025,5319,2210],{"class":1052},[1025,5321,1512],{"class":1035},[1025,5323,5324,5326,5328,5330],{"class":1027,"line":1065},[1025,5325,1782],{"class":1039},[1025,5327,1506],{"class":1694},[1025,5329,1305],{"class":1039},[1025,5331,1512],{"class":1035},[1025,5333,5334,5336,5338,5340],{"class":1027,"line":1113},[1025,5335,1793],{"class":1039},[1025,5337,1506],{"class":1694},[1025,5339,1724],{"class":1194},[1025,5341,1512],{"class":1035},[1025,5343,5344,5346,5348,5350],{"class":1027,"line":1126},[1025,5345,1804],{"class":1039},[1025,5347,1506],{"class":1694},[1025,5349,1952],{"class":1194},[1025,5351,1512],{"class":1035},[1025,5353,5354,5356,5358,5360],{"class":1027,"line":1138},[1025,5355,1959],{"class":1039},[1025,5357,1506],{"class":1694},[1025,5359,1964],{"class":1039},[1025,5361,1512],{"class":1035},[1025,5363,5364,5366,5368,5370],{"class":1027,"line":1159},[1025,5365,1971],{"class":1039},[1025,5367,1506],{"class":1694},[1025,5369,1976],{"class":1039},[1025,5371,1512],{"class":1035},[1025,5373,5374],{"class":1027,"line":1191},[1025,5375,1742],{"class":1035},[1581,5377,2270],{"id":2269},[856,5379,2273],{},[915,5381,5382,5396],{},[918,5383,5384],{},[921,5385,5386,5390,5394],{},[924,5387,5388],{},[860,5389,1075],{},[924,5391,5392],{},[860,5393,881],{},[924,5395,1613],{},[938,5397,5398],{},[921,5399,5400,5404,5408],{},[943,5401,5402],{},[860,5403,894],{},[943,5405,5406],{},[860,5407,894],{},[943,5409,5410],{},[860,5411,2306],{},[856,5413,1663],{},[1016,5415,5416],{"className":1018,"code":2311,"language":1020,"meta":1021,"style":1021},[860,5417,5418,5432,5442,5452,5462,5472],{"__ignoreMap":1021},[1025,5419,5420,5422,5424,5426,5428,5430],{"class":1027,"line":1028},[1025,5421,1673],{"class":1039},[1025,5423,1144],{"class":1035},[1025,5425,1678],{"class":1106},[1025,5427,1169],{"class":1035},[1025,5429,1683],{"class":1039},[1025,5431,1686],{"class":1035},[1025,5433,5434,5436,5438,5440],{"class":1027,"line":1059},[1025,5435,1766],{"class":1039},[1025,5437,1506],{"class":1694},[1025,5439,2336],{"class":1052},[1025,5441,1512],{"class":1035},[1025,5443,5444,5446,5448,5450],{"class":1027,"line":1065},[1025,5445,1782],{"class":1039},[1025,5447,1506],{"class":1694},[1025,5449,1305],{"class":1039},[1025,5451,1512],{"class":1035},[1025,5453,5454,5456,5458,5460],{"class":1027,"line":1113},[1025,5455,1793],{"class":1039},[1025,5457,1506],{"class":1694},[1025,5459,1724],{"class":1194},[1025,5461,1512],{"class":1035},[1025,5463,5464,5466,5468,5470],{"class":1027,"line":1126},[1025,5465,1804],{"class":1039},[1025,5467,1506],{"class":1694},[1025,5469,1724],{"class":1194},[1025,5471,1512],{"class":1035},[1025,5473,5474],{"class":1027,"line":1138},[1025,5475,1742],{"class":1035},[1581,5477,2376],{"id":2375},[856,5479,2379,5480,1824,5482,2385,5484,2389],{},[860,5481,2382],{},[860,5483,874],{},[860,5485,2388],{},[915,5487,5488,5502],{},[918,5489,5490],{},[921,5491,5492,5496,5500],{},[924,5493,5494],{},[860,5495,1075],{},[924,5497,5498],{},[860,5499,881],{},[924,5501,1613],{},[938,5503,5504],{},[921,5505,5506,5510,5514],{},[943,5507,5508],{},[860,5509,894],{},[943,5511,5512],{},[860,5513,888],{},[943,5515,5516],{},[860,5517,2422],{},[856,5519,1663],{},[1016,5521,5522],{"className":1018,"code":2427,"language":1020,"meta":1021,"style":1021},[860,5523,5524,5538,5552,5562,5572,5582,5596,5610],{"__ignoreMap":1021},[1025,5525,5526,5528,5530,5532,5534,5536],{"class":1027,"line":1028},[1025,5527,1673],{"class":1039},[1025,5529,1144],{"class":1035},[1025,5531,1678],{"class":1106},[1025,5533,1169],{"class":1035},[1025,5535,1683],{"class":1039},[1025,5537,1686],{"class":1035},[1025,5539,5540,5542,5544,5546,5548,5550],{"class":1027,"line":1059},[1025,5541,1766],{"class":1039},[1025,5543,1506],{"class":1694},[1025,5545,1049],{"class":1048},[1025,5547,2422],{"class":1052},[1025,5549,1172],{"class":1048},[1025,5551,1512],{"class":1035},[1025,5553,5554,5556,5558,5560],{"class":1027,"line":1065},[1025,5555,1782],{"class":1039},[1025,5557,1506],{"class":1694},[1025,5559,1305],{"class":1039},[1025,5561,1512],{"class":1035},[1025,5563,5564,5566,5568,5570],{"class":1027,"line":1113},[1025,5565,1793],{"class":1039},[1025,5567,1506],{"class":1694},[1025,5569,1724],{"class":1194},[1025,5571,1512],{"class":1035},[1025,5573,5574,5576,5578,5580],{"class":1027,"line":1126},[1025,5575,1804],{"class":1039},[1025,5577,1506],{"class":1694},[1025,5579,1952],{"class":1194},[1025,5581,1512],{"class":1035},[1025,5583,5584,5586,5588,5590,5592,5594],{"class":1027,"line":1138},[1025,5585,1959],{"class":1039},[1025,5587,1506],{"class":1694},[1025,5589,2496],{"class":1039},[1025,5591,1144],{"class":1035},[1025,5593,2501],{"class":1039},[1025,5595,1512],{"class":1035},[1025,5597,5598,5600,5602,5604,5606,5608],{"class":1027,"line":1159},[1025,5599,1971],{"class":1039},[1025,5601,1506],{"class":1694},[1025,5603,2496],{"class":1039},[1025,5605,1144],{"class":1035},[1025,5607,1426],{"class":1039},[1025,5609,1512],{"class":1035},[1025,5611,5612],{"class":1027,"line":1191},[1025,5613,1742],{"class":1035},[1581,5615,2525],{"id":2524},[856,5617,2528,5618,2531,5620,2536,5622,2540,5624,2544],{},[860,5619,1441],{},[1006,5621,2535],{"href":2534},[1410,5623,2539],{},[860,5625,2543],{},[915,5627,5628,5642],{},[918,5629,5630],{},[921,5631,5632,5636,5640],{},[924,5633,5634],{},[860,5635,1075],{},[924,5637,5638],{},[860,5639,881],{},[924,5641,1613],{},[938,5643,5644],{},[921,5645,5646,5650,5654],{},[943,5647,5648],{},[860,5649,894],{},[943,5651,5652],{},[860,5653,888],{},[943,5655,5656],{},[860,5657,2577],{},[856,5659,1319,5660,2582,5662,2586,5664,2590,5666,2594,5668,2598],{},[860,5661,2543],{},[860,5663,2585],{},[860,5665,2589],{},[860,5667,2593],{},[1006,5669,2597],{"href":514},[856,5671,1663],{},[1016,5673,5674],{"className":1018,"code":2603,"language":1020,"meta":1021,"style":1021},[860,5675,5676,5690,5704,5714,5724,5734,5748,5762],{"__ignoreMap":1021},[1025,5677,5678,5680,5682,5684,5686,5688],{"class":1027,"line":1028},[1025,5679,1673],{"class":1039},[1025,5681,1144],{"class":1035},[1025,5683,1678],{"class":1106},[1025,5685,1169],{"class":1035},[1025,5687,1683],{"class":1039},[1025,5689,1686],{"class":1035},[1025,5691,5692,5694,5696,5698,5700,5702],{"class":1027,"line":1059},[1025,5693,1766],{"class":1039},[1025,5695,1506],{"class":1694},[1025,5697,1049],{"class":1048},[1025,5699,2577],{"class":1052},[1025,5701,1172],{"class":1048},[1025,5703,1512],{"class":1035},[1025,5705,5706,5708,5710,5712],{"class":1027,"line":1065},[1025,5707,1782],{"class":1039},[1025,5709,1506],{"class":1694},[1025,5711,1305],{"class":1039},[1025,5713,1512],{"class":1035},[1025,5715,5716,5718,5720,5722],{"class":1027,"line":1113},[1025,5717,1793],{"class":1039},[1025,5719,1506],{"class":1694},[1025,5721,1724],{"class":1194},[1025,5723,1512],{"class":1035},[1025,5725,5726,5728,5730,5732],{"class":1027,"line":1126},[1025,5727,1804],{"class":1039},[1025,5729,1506],{"class":1694},[1025,5731,1952],{"class":1194},[1025,5733,1512],{"class":1035},[1025,5735,5736,5738,5740,5742,5744,5746],{"class":1027,"line":1138},[1025,5737,1959],{"class":1039},[1025,5739,1506],{"class":1694},[1025,5741,2496],{"class":1039},[1025,5743,1144],{"class":1035},[1025,5745,2501],{"class":1039},[1025,5747,1512],{"class":1035},[1025,5749,5750,5752,5754,5756,5758,5760],{"class":1027,"line":1159},[1025,5751,1971],{"class":1039},[1025,5753,1506],{"class":1694},[1025,5755,2496],{"class":1039},[1025,5757,1144],{"class":1035},[1025,5759,1426],{"class":1039},[1025,5761,1512],{"class":1035},[1025,5763,5764],{"class":1027,"line":1191},[1025,5765,1742],{"class":1035},[1581,5767,2699],{"id":2698},[856,5769,2702,5770,2706,5772,2709,5774,1645,5776,2716,5778,2719,5780,2722,5782,2726],{},[860,5771,2705],{},[1006,5773,399],{"href":35},[860,5775,2712],{},[860,5777,2715],{},[860,5779,2715],{},[860,5781,888],{},[860,5783,2725],{},[856,5785,2729,5786,1645,5788,2736,5790,2739],{},[860,5787,2732],{},[860,5789,2735],{},[860,5791,874],{},[915,5793,5794,5810],{},[918,5795,5796],{},[921,5797,5798,5800,5802,5806],{},[924,5799,2748],{},[924,5801,2751],{},[924,5803,5804],{},[860,5805,1075],{},[924,5807,5808],{},[860,5809,881],{},[938,5811,5812,5828,5844,5860],{},[921,5813,5814,5816,5820,5824],{},[943,5815,2766],{},[943,5817,5818],{},[860,5819,2771],{},[943,5821,5822],{},[860,5823,888],{},[943,5825,5826],{},[860,5827,894],{},[921,5829,5830,5832,5836,5840],{},[943,5831,2766],{},[943,5833,5834],{},[860,5835,2788],{},[943,5837,5838],{},[860,5839,894],{},[943,5841,5842],{},[860,5843,888],{},[921,5845,5846,5848,5852,5856],{},[943,5847,2801],{},[943,5849,5850],{},[860,5851,2806],{},[943,5853,5854],{},[860,5855,888],{},[943,5857,5858],{},[860,5859,894],{},[921,5861,5862,5864,5868,5872],{},[943,5863,2801],{},[943,5865,5866],{},[860,5867,2823],{},[943,5869,5870],{},[860,5871,894],{},[943,5873,5874],{},[860,5875,888],{},[1637,5877,5878],{},[856,5879,2836,5880,2839,5882,2843,5884,2847],{},[860,5881,888],{},[860,5883,2842],{},[1410,5885,2846],{},[856,5887,2850,5888,1824,5890,2855,5892,2858,5894,2862,5896,1645,5898,2867,5900,2870],{},[860,5889,894],{},[860,5891,874],{},[860,5893,888],{},[1006,5895,2861],{"href":124},[860,5897,2732],{},[860,5899,2735],{},[860,5901,888],{},[856,5903,1663],{},[1016,5905,5906],{"className":1018,"code":2875,"language":1020,"meta":1021,"style":1021},[860,5907,5908,5922,5936,5946,5956,5966,5980,5994],{"__ignoreMap":1021},[1025,5909,5910,5912,5914,5916,5918,5920],{"class":1027,"line":1028},[1025,5911,1673],{"class":1039},[1025,5913,1144],{"class":1035},[1025,5915,1678],{"class":1106},[1025,5917,1169],{"class":1035},[1025,5919,1683],{"class":1039},[1025,5921,1686],{"class":1035},[1025,5923,5924,5926,5928,5930,5932,5934],{"class":1027,"line":1059},[1025,5925,1766],{"class":1039},[1025,5927,1506],{"class":1694},[1025,5929,1049],{"class":1048},[1025,5931,2902],{"class":1052},[1025,5933,1172],{"class":1048},[1025,5935,1512],{"class":1035},[1025,5937,5938,5940,5942,5944],{"class":1027,"line":1065},[1025,5939,1782],{"class":1039},[1025,5941,1506],{"class":1694},[1025,5943,1305],{"class":1039},[1025,5945,1512],{"class":1035},[1025,5947,5948,5950,5952,5954],{"class":1027,"line":1113},[1025,5949,1793],{"class":1039},[1025,5951,1506],{"class":1694},[1025,5953,1724],{"class":1194},[1025,5955,1512],{"class":1035},[1025,5957,5958,5960,5962,5964],{"class":1027,"line":1126},[1025,5959,1804],{"class":1039},[1025,5961,1506],{"class":1694},[1025,5963,1952],{"class":1194},[1025,5965,1512],{"class":1035},[1025,5967,5968,5970,5972,5974,5976,5978],{"class":1027,"line":1138},[1025,5969,1959],{"class":1039},[1025,5971,1506],{"class":1694},[1025,5973,2496],{"class":1039},[1025,5975,1144],{"class":1035},[1025,5977,2501],{"class":1039},[1025,5979,1512],{"class":1035},[1025,5981,5982,5984,5986,5988,5990,5992],{"class":1027,"line":1159},[1025,5983,1971],{"class":1039},[1025,5985,1506],{"class":1694},[1025,5987,2496],{"class":1039},[1025,5989,1144],{"class":1035},[1025,5991,1426],{"class":1039},[1025,5993,1512],{"class":1035},[1025,5995,5996],{"class":1027,"line":1191},[1025,5997,1742],{"class":1035},[1016,5999,6000],{"className":1018,"code":2971,"language":1020,"meta":1021,"style":1021},[860,6001,6002,6016,6030,6040,6050,6060,6074,6088],{"__ignoreMap":1021},[1025,6003,6004,6006,6008,6010,6012,6014],{"class":1027,"line":1028},[1025,6005,1673],{"class":1039},[1025,6007,1144],{"class":1035},[1025,6009,1678],{"class":1106},[1025,6011,1169],{"class":1035},[1025,6013,1683],{"class":1039},[1025,6015,1686],{"class":1035},[1025,6017,6018,6020,6022,6024,6026,6028],{"class":1027,"line":1059},[1025,6019,1766],{"class":1039},[1025,6021,1506],{"class":1694},[1025,6023,1049],{"class":1048},[1025,6025,2998],{"class":1052},[1025,6027,1172],{"class":1048},[1025,6029,1512],{"class":1035},[1025,6031,6032,6034,6036,6038],{"class":1027,"line":1065},[1025,6033,1782],{"class":1039},[1025,6035,1506],{"class":1694},[1025,6037,1305],{"class":1039},[1025,6039,1512],{"class":1035},[1025,6041,6042,6044,6046,6048],{"class":1027,"line":1113},[1025,6043,1793],{"class":1039},[1025,6045,1506],{"class":1694},[1025,6047,1952],{"class":1194},[1025,6049,1512],{"class":1035},[1025,6051,6052,6054,6056,6058],{"class":1027,"line":1126},[1025,6053,1804],{"class":1039},[1025,6055,1506],{"class":1694},[1025,6057,1724],{"class":1194},[1025,6059,1512],{"class":1035},[1025,6061,6062,6064,6066,6068,6070,6072],{"class":1027,"line":1138},[1025,6063,1959],{"class":1039},[1025,6065,1506],{"class":1694},[1025,6067,2496],{"class":1039},[1025,6069,1144],{"class":1035},[1025,6071,2501],{"class":1039},[1025,6073,1512],{"class":1035},[1025,6075,6076,6078,6080,6082,6084,6086],{"class":1027,"line":1159},[1025,6077,1971],{"class":1039},[1025,6079,1506],{"class":1694},[1025,6081,2496],{"class":1039},[1025,6083,1144],{"class":1035},[1025,6085,1426],{"class":1039},[1025,6087,1512],{"class":1035},[1025,6089,6090],{"class":1027,"line":1191},[1025,6091,1742],{"class":1035},[1581,6093,3068],{"id":3067},[856,6095,3071,6096,3075,6098,3079],{},[860,6097,3074],{},[860,6099,3078],{},[856,6101,6102,3095],{},[1410,6103,3084,6104,867,6106,3091,6108,1144],{},[860,6105,3087],{},[860,6107,3090],{},[860,6109,3094],{},[856,6111,3098],{},[915,6113,6114,6122],{},[918,6115,6116],{},[921,6117,6118,6120],{},[924,6119,3107],{},[924,6121,3110],{},[938,6123,6124,6150,6162],{},[921,6125,6126,6128],{},[943,6127,3117],{},[943,6129,6130,867,6132,867,6134,867,6136,867,6138,867,6140,867,6142,867,6144,867,6146,867,6148],{},[860,6131,3122],{},[860,6133,3125],{},[860,6135,3128],{},[860,6137,3131],{},[860,6139,3134],{},[860,6141,3137],{},[860,6143,3140],{},[860,6145,3143],{},[860,6147,3146],{},[860,6149,3149],{},[921,6151,6152,6154],{},[943,6153,3154],{},[943,6155,6156,867,6158,867,6160],{},[860,6157,3159],{},[860,6159,3162],{},[860,6161,3165],{},[921,6163,6164,6166],{},[943,6165,3170],{},[943,6167,6168,867,6170,867,6172,867,6174],{},[860,6169,3175],{},[860,6171,3178],{},[860,6173,3181],{},[860,6175,3184],{},[856,6177,3187],{},[915,6179,6180,6194],{},[918,6181,6182],{},[921,6183,6184,6188,6192],{},[924,6185,6186],{},[860,6187,1075],{},[924,6189,6190],{},[860,6191,881],{},[924,6193,1613],{},[938,6195,6196],{},[921,6197,6198,6202,6206],{},[943,6199,6200],{},[860,6201,894],{},[943,6203,6204],{},[860,6205,888],{},[943,6207,6208],{},[860,6209,3220],{},[856,6211,1663],{},[1016,6213,6214],{"className":1018,"code":3225,"language":1020,"meta":1021,"style":1021},[860,6215,6216,6230,6244,6254,6264,6274,6288,6302],{"__ignoreMap":1021},[1025,6217,6218,6220,6222,6224,6226,6228],{"class":1027,"line":1028},[1025,6219,1673],{"class":1039},[1025,6221,1144],{"class":1035},[1025,6223,1678],{"class":1106},[1025,6225,1169],{"class":1035},[1025,6227,1683],{"class":1039},[1025,6229,1686],{"class":1035},[1025,6231,6232,6234,6236,6238,6240,6242],{"class":1027,"line":1059},[1025,6233,1766],{"class":1039},[1025,6235,1506],{"class":1694},[1025,6237,1049],{"class":1048},[1025,6239,3220],{"class":1052},[1025,6241,1172],{"class":1048},[1025,6243,1512],{"class":1035},[1025,6245,6246,6248,6250,6252],{"class":1027,"line":1065},[1025,6247,1782],{"class":1039},[1025,6249,1506],{"class":1694},[1025,6251,1305],{"class":1039},[1025,6253,1512],{"class":1035},[1025,6255,6256,6258,6260,6262],{"class":1027,"line":1113},[1025,6257,1793],{"class":1039},[1025,6259,1506],{"class":1694},[1025,6261,1724],{"class":1194},[1025,6263,1512],{"class":1035},[1025,6265,6266,6268,6270,6272],{"class":1027,"line":1126},[1025,6267,1804],{"class":1039},[1025,6269,1506],{"class":1694},[1025,6271,1952],{"class":1194},[1025,6273,1512],{"class":1035},[1025,6275,6276,6278,6280,6282,6284,6286],{"class":1027,"line":1138},[1025,6277,1959],{"class":1039},[1025,6279,1506],{"class":1694},[1025,6281,2496],{"class":1039},[1025,6283,1144],{"class":1035},[1025,6285,2501],{"class":1039},[1025,6287,1512],{"class":1035},[1025,6289,6290,6292,6294,6296,6298,6300],{"class":1027,"line":1159},[1025,6291,1971],{"class":1039},[1025,6293,1506],{"class":1694},[1025,6295,2496],{"class":1039},[1025,6297,1144],{"class":1035},[1025,6299,1426],{"class":1039},[1025,6301,1512],{"class":1035},[1025,6303,6304],{"class":1027,"line":1191},[1025,6305,1742],{"class":1035},[856,6307,3320,6308,3324],{},[860,6309,3323],{},[856,6311,3327],{},[1016,6313,6314],{"className":1018,"code":3330,"language":1020,"meta":1021,"style":1021},[860,6315,6316,6330,6344,6354,6364,6374,6388,6402],{"__ignoreMap":1021},[1025,6317,6318,6320,6322,6324,6326,6328],{"class":1027,"line":1028},[1025,6319,1673],{"class":1039},[1025,6321,1144],{"class":1035},[1025,6323,1678],{"class":1106},[1025,6325,1169],{"class":1035},[1025,6327,1683],{"class":1039},[1025,6329,1686],{"class":1035},[1025,6331,6332,6334,6336,6338,6340,6342],{"class":1027,"line":1059},[1025,6333,1766],{"class":1039},[1025,6335,1506],{"class":1694},[1025,6337,1049],{"class":1048},[1025,6339,3357],{"class":1052},[1025,6341,1172],{"class":1048},[1025,6343,1512],{"class":1035},[1025,6345,6346,6348,6350,6352],{"class":1027,"line":1065},[1025,6347,1782],{"class":1039},[1025,6349,1506],{"class":1694},[1025,6351,1305],{"class":1039},[1025,6353,1512],{"class":1035},[1025,6355,6356,6358,6360,6362],{"class":1027,"line":1113},[1025,6357,1793],{"class":1039},[1025,6359,1506],{"class":1694},[1025,6361,1952],{"class":1194},[1025,6363,1512],{"class":1035},[1025,6365,6366,6368,6370,6372],{"class":1027,"line":1126},[1025,6367,1804],{"class":1039},[1025,6369,1506],{"class":1694},[1025,6371,1724],{"class":1194},[1025,6373,1512],{"class":1035},[1025,6375,6376,6378,6380,6382,6384,6386],{"class":1027,"line":1138},[1025,6377,1959],{"class":1039},[1025,6379,1506],{"class":1694},[1025,6381,2496],{"class":1039},[1025,6383,1144],{"class":1035},[1025,6385,2501],{"class":1039},[1025,6387,1512],{"class":1035},[1025,6389,6390,6392,6394,6396,6398,6400],{"class":1027,"line":1159},[1025,6391,1971],{"class":1039},[1025,6393,1506],{"class":1694},[1025,6395,2496],{"class":1039},[1025,6397,1144],{"class":1035},[1025,6399,1426],{"class":1039},[1025,6401,1512],{"class":1035},[1025,6403,6404],{"class":1027,"line":1191},[1025,6405,1742],{"class":1035},[897,6407],{},[900,6409,3429],{"id":3428},[856,6411,3432,6412,3435,6414,3438,6416,3442],{},[860,6413,874],{},[1006,6415,399],{"href":404},[860,6417,3441],{},[3444,6419,6420,6424,6426,6428,6430],{},[1407,6421,3448,6422,3451],{},[860,6423,1322],{},[1407,6425,3454],{},[1407,6427,3457],{},[1407,6429,3460],{},[1407,6431,3463,6432,2531,6434],{},[860,6433,1441],{},[1006,6435,3468],{"href":435},[856,6437,3471,6438,1506],{},[860,6439,3474],{},[915,6441,6442,6452],{},[918,6443,6444],{},[921,6445,6446,6448,6450],{},[924,6447,3483],{},[924,6449,936],{},[924,6451,3488],{},[938,6453,6454,6474,6500],{},[921,6455,6456,6460,6468],{},[943,6457,6458],{},[860,6459,3497],{},[943,6461,3500,6462,3504,6464,871,6466,3509],{},[860,6463,3503],{},[860,6465,2712],{},[860,6467,2715],{},[943,6469,6470,3514,6472,3518],{},[860,6471,908],{},[860,6473,3517],{},[921,6475,6476,6480,6494],{},[943,6477,6478],{},[860,6479,3525],{},[943,6481,3500,6482,3531,6484,867,6486,867,6488,867,6490,867,6492,3544],{},[860,6483,3530],{},[860,6485,3159],{},[860,6487,3175],{},[860,6489,3184],{},[860,6491,3540],{},[860,6493,3543],{},[943,6495,6496,3549,6498,3518],{},[860,6497,908],{},[860,6499,3517],{},[921,6501,6502,6506,6508],{},[943,6503,6504],{},[860,6505,3558],{},[943,6507,3561],{},[943,6509,6510,3567],{},[860,6511,3566],{},[856,6513,1319,6514,3572,6516,1645,6518,3577,6520,3581,6522,1645,6524,3586,6526,3589,6528,3592],{},[860,6515,3517],{},[860,6517,3074],{},[860,6519,3078],{},[860,6521,3580],{},[860,6523,3074],{},[860,6525,3078],{},[860,6527,3580],{},[860,6529,908],{},[1581,6531,1327],{"id":3595},[856,6533,1319,6534,3600],{},[860,6535,1322],{},[3602,6537,6538,6540,6546,6548,6566,6568,6576,6578],{"level":3604},[3606,6539,3609],{"id":3608},[856,6541,3612,6542,3616,6544,3619],{},[860,6543,3615],{},[860,6545,1322],{},[3606,6547,103],{"id":3622},[856,6549,3625,6550,3628,6552,3632,6554,3636,6556,3639,6558,3643,6560,3647,6562,3650,6564,1144],{},[860,6551,1322],{},[860,6553,3631],{},[860,6555,3635],{},[860,6557,1426],{},[860,6559,3642],{},[860,6561,3646],{},[860,6563,1426],{},[1006,6565,3654],{"href":3653},[3606,6567,3658],{"id":3657},[856,6569,1319,6570,3663,6572,3666,6574,3669],{},[860,6571,964],{},[860,6573,1322],{},[860,6575,862],{},[3606,6577,3673],{"id":3672},[856,6579,3676,6580,3679,6582,1645,6584,1448,6586,3686,6588,3689],{},[860,6581,1430],{},[860,6583,2732],{},[860,6585,2735],{},[860,6587,888],{},[860,6589,1322],{},[897,6591],{},[900,6593,3695],{"id":3694},[856,6595,3698,6596,3701,6598,3704,6600,3707],{},[860,6597,1430],{},[860,6599,870],{},[860,6601,2175],{},[1016,6603,6604],{"className":1018,"code":3710,"language":1020,"meta":1021,"style":1021},[860,6605,6606,6610,6614,6622,6626],{"__ignoreMap":1021},[1025,6607,6608],{"class":1027,"line":1028},[1025,6609,3717],{"class":1035},[1025,6611,6612],{"class":1027,"line":1059},[1025,6613,3722],{"class":1035},[1025,6615,6616,6618,6620],{"class":1027,"line":1065},[1025,6617,3727],{"class":1035},[1025,6619,3731],{"class":3730},[1025,6621,3734],{"class":1122},[1025,6623,6624],{"class":1027,"line":1113},[1025,6625,3739],{"class":1035},[1025,6627,6628],{"class":1027,"line":1126},[1025,6629,1269],{"class":1035},[856,6631,3746],{},[3748,6633,6634],{},[856,6635,3752,6636,1448,6638,3758,6640,3761],{},[860,6637,1430],{},[860,6639,3757],{},[860,6641,2175],{},[897,6643],{},[900,6645,3654],{"id":3766},[856,6647,1319,6648,3771,6650,3774,6652,3777],{},[860,6649,3635],{},[860,6651,888],{},[860,6653,3646],{},[3444,6655,6656,6662],{},[1407,6657,3782,6658,3785,6660],{},[860,6659,1454],{},[860,6661,1322],{},[1407,6663,3790,6664,2706,6666,3796],{},[860,6665,3793],{},[1006,6667,399],{"href":35},[856,6669,3799],{},[856,6671,3802,6672,3805],{},[1006,6673,111],{"href":112},[897,6675],{},[900,6677,3811],{"id":3810},[1016,6679,6680],{"className":1018,"code":3814,"language":1020,"meta":1021,"style":1021},[860,6681,6682,6690,6698,6708,6718,6728,6738],{"__ignoreMap":1021},[1025,6683,6684,6686,6688],{"class":1027,"line":1028},[1025,6685,1491],{"class":1068},[1025,6687,3823],{"class":1494},[1025,6689,1498],{"class":1035},[1025,6691,6692,6694,6696],{"class":1027,"line":1059},[1025,6693,3830],{"class":1039},[1025,6695,1506],{"class":1099},[1025,6697,3835],{"class":1494},[1025,6699,6700,6702,6704,6706],{"class":1027,"line":1065},[1025,6701,3840],{"class":1039},[1025,6703,1506],{"class":1099},[1025,6705,1509],{"class":1494},[1025,6707,3847],{"class":1122},[1025,6709,6710,6712,6714,6716],{"class":1027,"line":1113},[1025,6711,3852],{"class":1039},[1025,6713,1506],{"class":1099},[1025,6715,1552],{"class":1494},[1025,6717,3859],{"class":1122},[1025,6719,6720,6722,6724,6726],{"class":1027,"line":1126},[1025,6721,1959],{"class":1039},[1025,6723,1530],{"class":1099},[1025,6725,3868],{"class":1494},[1025,6727,3871],{"class":1122},[1025,6729,6730,6732,6734,6736],{"class":1027,"line":1138},[1025,6731,1971],{"class":1039},[1025,6733,1530],{"class":1099},[1025,6735,1509],{"class":1494},[1025,6737,3882],{"class":1122},[1025,6739,6740],{"class":1027,"line":1159},[1025,6741,1269],{"class":1035},[856,6743,979,6744,885,6746,867,6748,1645,6750,3897,6752,3900],{},[860,6745,881],{},[860,6747,888],{},[860,6749,1089],{},[860,6751,1094],{},[1006,6753,123],{"href":124},[856,6755,979,6756,885,6758,1645,6760,885,6762,3911],{},[860,6757,881],{},[860,6759,894],{},[860,6761,1075],{},[860,6763,894],{},[897,6765],{},[900,6767,3917],{"id":3916},[915,6769,6770,6784],{},[918,6771,6772],{},[921,6773,6774,6776,6778,6782],{},[924,6775,3926],{},[924,6777,3929],{},[924,6779,6780],{},[860,6781,881],{},[924,6783,3936],{},[938,6785,6786,6798,6812,6826,6840,6852,6864,6878,6890],{},[921,6787,6788,6790,6792,6796],{},[943,6789,3943],{},[943,6791,3946],{},[943,6793,6794],{},[860,6795,894],{},[943,6797,3953],{},[921,6799,6800,6802,6806,6810],{},[943,6801,3958],{},[943,6803,3961,6804,1305],{},[860,6805,1322],{},[943,6807,6808],{},[860,6809,888],{},[943,6811,3970],{},[921,6813,6814,6816,6820,6824],{},[943,6815,3975],{},[943,6817,6818,3980],{},[860,6819,1992],{},[943,6821,6822],{},[860,6823,888],{},[943,6825,3987],{},[921,6827,6828,6830,6834,6838],{},[943,6829,3992],{},[943,6831,6832],{},[860,6833,3997],{},[943,6835,6836],{},[860,6837,888],{},[943,6839,4004],{},[921,6841,6842,6844,6846,6850],{},[943,6843,4009],{},[943,6845,4012],{},[943,6847,6848],{},[860,6849,894],{},[943,6851,4019],{},[921,6853,6854,6856,6858,6862],{},[943,6855,4024],{},[943,6857,4027],{},[943,6859,6860],{},[860,6861,888],{},[943,6863,4034],{},[921,6865,6866,6868,6872,6876],{},[943,6867,4039],{},[943,6869,4042,6870],{},[860,6871,4045],{},[943,6873,6874],{},[860,6875,888],{},[943,6877,4052],{},[921,6879,6880,6882,6884,6888],{},[943,6881,4057],{},[943,6883,4060],{},[943,6885,6886],{},[860,6887,888],{},[943,6889,4067],{},[921,6891,6892,6894,6896,6900],{},[943,6893,4072],{},[943,6895,4075],{},[943,6897,6898],{},[860,6899,888],{},[943,6901,4082],{},[897,6903],{},[900,6905,4088],{"id":4087},[856,6907,4091],{},[915,6909,6910,6924],{},[918,6911,6912],{},[921,6913,6914,6916,6918,6920,6922],{},[924,6915,4100],{},[924,6917,4103],{},[924,6919,1281],{},[924,6921,4108],{},[924,6923,1284],{},[938,6925,6926,6944,6964,6982,7002],{},[921,6927,6928,6932,6936,6940,6942],{},[943,6929,6930],{},[860,6931,2135],{},[943,6933,6934],{},[860,6935,4123],{},[943,6937,6938],{},[860,6939,4128],{},[943,6941,4131],{},[943,6943,4134],{},[921,6945,6946,6950,6954,6958,6960],{},[943,6947,6948],{},[860,6949,2175],{},[943,6951,6952],{},[860,6953,4123],{},[943,6955,6956],{},[860,6957,4128],{},[943,6959,4131],{},[943,6961,4153,6962,4156],{},[860,6963,1430],{},[921,6965,6966,6970,6974,6978,6980],{},[943,6967,6968],{},[860,6969,4163],{},[943,6971,6972],{},[860,6973,4168],{},[943,6975,6976],{},[860,6977,1372],{},[943,6979,4131],{},[943,6981,4177],{},[921,6983,6984,6988,6992,6996,7000],{},[943,6985,6986],{},[860,6987,2543],{},[943,6989,6990],{},[860,6991,4188],{},[943,6993,6994],{},[860,6995,4128],{},[943,6997,6998],{},[860,6999,2585],{},[943,7001,4199],{},[921,7003,7004,7008,7010,7014,7018],{},[943,7005,7006],{},[860,7007,3635],{},[943,7009,4208],{},[943,7011,7012],{},[860,7013,1372],{},[943,7015,7016],{},[860,7017,894],{},[943,7019,4219],{},[856,7021,1004,7022,4224,7024,4227],{},[1006,7023,237],{"href":238},[1006,7025,2597],{"href":514},[4229,7027,4231],{},{"title":1021,"searchDepth":1059,"depth":1059,"links":7029},[7030,7031,7032,7033,7044,7047,7048,7049,7050,7051],{"id":902,"depth":1059,"text":903},{"id":1013,"depth":1059,"text":1014},{"id":1385,"depth":1059,"text":1386},{"id":1578,"depth":1059,"text":1579,"children":7034},[7035,7036,7037,7038,7039,7040,7041,7042,7043],{"id":1583,"depth":1065,"text":1584},{"id":1817,"depth":1065,"text":1818},{"id":1985,"depth":1065,"text":1986},{"id":2128,"depth":1065,"text":2129},{"id":2269,"depth":1065,"text":2270},{"id":2375,"depth":1065,"text":2376},{"id":2524,"depth":1065,"text":2525},{"id":2698,"depth":1065,"text":2699},{"id":3067,"depth":1065,"text":3068},{"id":3428,"depth":1059,"text":3429,"children":7045},[7046],{"id":3595,"depth":1065,"text":1327},{"id":3694,"depth":1059,"text":3695},{"id":3766,"depth":1059,"text":3654},{"id":3810,"depth":1059,"text":3811},{"id":3916,"depth":1059,"text":3917},{"id":4087,"depth":1059,"text":4088},{},{"title":95,"description":4256},1780436280703]