[{"data":1,"prerenderedAt":4542},["ShallowReactive",2],{"navLinks":3,"sidebar_docs_navigation_\u002Fdocs\u002Fiam":64,"navigation":257,"navLinks_footer":837,"\u002Fdocs\u002Fiam\u002Fessentials\u002Frefresh-tokens_page":850,"\u002Fdocs\u002Fiam\u002Fessentials\u002Frefresh-tokens_surround":2941,"\u002Fdocs\u002Fiam\u002Fessentials\u002Frefresh-tokens":2944},{"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":91,"body":852,"description":2933,"extension":2934,"icon":2935,"meta":2936,"module":2937,"navigation":8,"path":92,"rawbody":2938,"seo":2939,"stem":93,"__hash__":2940},"docs\u002Fdocs\u002Fiam\u002F01.essentials\u002F02.refresh-tokens.md",{"type":853,"value":854,"toc":2905},"minimark",[855,872,887,890,895,905,1062,1071,1073,1077,1210,1276,1293,1299,1301,1305,1315,1328,1330,1334,1337,1344,1350,1457,1552,1557,1582,1594,1599,1602,1678,1757,1771,1778,1828,1845,1847,1850,1854,1906,1912,1916,1967,1973,1975,1978,1981,1987,1990,2101,2122,2128,2131,2209,2212,2220,2232,2236,2243,2251,2257,2372,2379,2458,2460,2463,2469,2475,2478,2517,2522,2565,2567,2571,2583,2589,2602,2606,2626,2630,2635,2651,2656,2663,2665,2669,2672,2700,2718,2727,2729,2733,2739,2747,2758,2763,2771,2773,2777,2786,2879,2901],[856,857,858,859,863,864,867,868,871],"p",{},"Refresh tokens are opaque random strings, 64 bytes, hex encoded. The service stores a SHA-256 hash of each token in a MySQL ",[860,861,862],"code",{},"refresh_tokens"," table alongside usage metadata. The raw token is sent to the client exactly once, as an ",[860,865,866],{},"httpOnly"," cookie named ",[860,869,870],{},"session",", and never stored in plaintext on the server.",[856,873,874,875,878,879,882,883,886],{},"Every refresh token has a ",[860,876,877],{},"usage_count"," column. That column is the core of the reuse detection system. A token starts at ",[860,880,881],{},"0",", gets atomically changed to ",[860,884,885],{},"1"," on consumption, and any second attempt with the same token revokes every active session for that user.",[888,889],"hr",{},[891,892,894],"h2",{"id":893},"the-database-row","The Database Row",[856,896,897,898,900,901,904],{},"Each token maps to one row in the ",[860,899,862],{}," table. Here is what gets stored when ",[860,902,903],{},"generateRefreshToken"," runs:",[906,907,908,924],"table",{},[909,910,911],"thead",{},[912,913,914,918,921],"tr",{},[915,916,917],"th",{},"Column",[915,919,920],{},"Type",[915,922,923],{},"Description",[925,926,927,943,962,977,992,1007,1025,1044],"tbody",{},[912,928,929,935,940],{},[930,931,932],"td",{},[860,933,934],{},"id",[930,936,937],{},[860,938,939],{},"INT AUTO_INCREMENT",[930,941,942],{},"Primary key",[912,944,945,950,955],{},[930,946,947],{},[860,948,949],{},"user_id",[930,951,952],{},[860,953,954],{},"INT NOT NULL",[930,956,957,958,961],{},"Foreign key to ",[860,959,960],{},"users(id)",", cascades on delete",[912,963,964,969,974],{},[930,965,966],{},[860,967,968],{},"token",[930,970,971],{},[860,972,973],{},"VARCHAR(600) UNIQUE",[930,975,976],{},"SHA-256 hex hash of the raw token",[912,978,979,984,989],{},[930,980,981],{},[860,982,983],{},"valid",[930,985,986],{},[860,987,988],{},"BOOLEAN DEFAULT 0",[930,990,991],{},"Whether the token is still usable",[912,993,994,999,1004],{},[930,995,996],{},[860,997,998],{},"created_at",[930,1000,1001],{},[860,1002,1003],{},"TIMESTAMP",[930,1005,1006],{},"When the row was inserted",[912,1008,1009,1014,1018],{},[930,1010,1011],{},[860,1012,1013],{},"expiresAt",[930,1015,1016],{},[860,1017,1003],{},[930,1019,1020,1021,1024],{},"Computed from ",[860,1022,1023],{},"refresh_ttl"," at insert time",[912,1026,1027,1031,1036],{},[930,1028,1029],{},[860,1030,877],{},[930,1032,1033],{},[860,1034,1035],{},"INT DEFAULT 0",[930,1037,1038,1040,1041,1043],{},[860,1039,881],{}," = fresh, ",[860,1042,885],{}," = consumed, used by reuse detection",[912,1045,1046,1051,1055],{},[930,1047,1048],{},[860,1049,1050],{},"session_started_at",[930,1052,1053],{},[860,1054,1003],{},[930,1056,1057,1058,1061],{},"Set once at creation and carried forward to each new token on rotation. Used for ",[860,1059,1060],{},"MAX_SESSION_LIFE"," enforcement",[856,1063,1064,1065,1067,1068,1070],{},"The ",[860,1066,968],{}," column is unique. The ",[860,1069,949],{}," foreign key cascades on delete, so deleting a user wipes all their sessions automatically.",[888,1072],{},[891,1074,1076],{"id":1075},"generating-a-token","Generating a Token",[1078,1079,1084],"pre",{"className":1080,"code":1081,"language":1082,"meta":1083,"style":1083},"language-ts shiki shiki-themes light-plus light-plus dracula","import { generateRefreshToken } from '@riavzon\u002Fauth'\n\nconst { raw, expiresAt } = await generateRefreshToken(\n  config.jwt.refresh_tokens.refresh_ttl,\n  user.id,\n  prevTs \u002F\u002F optional\n)\n","ts","",[860,1085,1086,1119,1125,1159,1182,1194,1204],{"__ignoreMap":1083},[1087,1088,1091,1095,1099,1102,1105,1108,1112,1116],"span",{"class":1089,"line":1090},"line",1,[1087,1092,1094],{"class":1093},"sZ328","import",[1087,1096,1098],{"class":1097},"sDd4n"," { ",[1087,1100,903],{"class":1101},"sjsA6",[1087,1103,1104],{"class":1097}," } ",[1087,1106,1107],{"class":1093},"from",[1087,1109,1111],{"class":1110},"sFkSl"," '",[1087,1113,1115],{"class":1114},"sFB1V","@riavzon\u002Fauth",[1087,1117,1118],{"class":1110},"'\n",[1087,1120,1122],{"class":1089,"line":1121},2,[1087,1123,1124],{"emptyLinePlaceholder":8},"\n",[1087,1126,1128,1132,1134,1138,1141,1143,1145,1149,1152,1156],{"class":1089,"line":1127},3,[1087,1129,1131],{"class":1130},"sl46w","const",[1087,1133,1098],{"class":1097},[1087,1135,1137],{"class":1136},"s3JHE","raw",[1087,1139,1140],{"class":1097},", ",[1087,1142,1013],{"class":1136},[1087,1144,1104],{"class":1097},[1087,1146,1148],{"class":1147},"saOXh","=",[1087,1150,1151],{"class":1093}," await",[1087,1153,1155],{"class":1154},"sHOzp"," generateRefreshToken",[1087,1157,1158],{"class":1097},"(\n",[1087,1160,1162,1165,1168,1171,1173,1175,1177,1179],{"class":1089,"line":1161},4,[1087,1163,1164],{"class":1101},"  config",[1087,1166,1167],{"class":1097},".",[1087,1169,1170],{"class":1101},"jwt",[1087,1172,1167],{"class":1097},[1087,1174,862],{"class":1101},[1087,1176,1167],{"class":1097},[1087,1178,1023],{"class":1101},[1087,1180,1181],{"class":1097},",\n",[1087,1183,1185,1188,1190,1192],{"class":1089,"line":1184},5,[1087,1186,1187],{"class":1101},"  user",[1087,1189,1167],{"class":1097},[1087,1191,934],{"class":1101},[1087,1193,1181],{"class":1097},[1087,1195,1197,1200],{"class":1089,"line":1196},6,[1087,1198,1199],{"class":1101},"  prevTs",[1087,1201,1203],{"class":1202},"sghk6"," \u002F\u002F optional\n",[1087,1205,1207],{"class":1089,"line":1206},7,[1087,1208,1209],{"class":1097},")\n",[906,1211,1212,1223],{},[909,1213,1214],{},[912,1215,1216,1219,1221],{},[915,1217,1218],{},"Parameter",[915,1220,920],{},[915,1222,923],{},[925,1224,1225,1240,1254],{},[912,1226,1227,1232,1237],{},[930,1228,1229],{},[860,1230,1231],{},"ttl",[930,1233,1234],{},[860,1235,1236],{},"number",[930,1238,1239],{},"Lifetime in milliseconds",[912,1241,1242,1247,1251],{},[930,1243,1244],{},[860,1245,1246],{},"userId",[930,1248,1249],{},[860,1250,1236],{},[930,1252,1253],{},"User ID to associate the token with",[912,1255,1256,1261,1270],{},[930,1257,1258],{},[860,1259,1260],{},"prevTs",[930,1262,1263,1266,1267],{},[860,1264,1265],{},"Date"," OR ",[860,1268,1269],{},"undefined",[930,1271,1272,1273],{},"The previous timestamp of the session to set in the db, defaults to ",[860,1274,1275],{},"UTC_TIMESTAMP()",[856,1277,1278,1279,1282,1283,1286,1287,1289,1290,1292],{},"The function generates 64 random bytes, hex encodes them, computes the SHA-256 hash with ",[860,1280,1281],{},"toDigestHex",", then inserts a row with ",[860,1284,1285],{},"valid = 1"," and ",[860,1288,1013],{}," computed from the provided TTL. It returns the raw unhashed token and the expiry date. That raw value is what goes into the ",[860,1291,870],{}," cookie. The server never sees it again after this point.",[856,1294,1295,1296,1298],{},"When using the standalone service, the service generates and sends refresh tokens automatically on signup, login, MFA completion, and token rotation. You do not call ",[860,1297,903],{}," directly in that mode.",[888,1300],{},[891,1302,1304],{"id":1303},"hashing","Hashing",[856,1306,1307,1308,1310,1311,1314],{},"All refresh token functions accept a raw token string and hash it internally before any database operation. The ",[860,1309,1281],{}," utility is a function that checks whether the input is already a SHA-256 hex string. If it is, it passes through. If not, it computes ",[860,1312,1313],{},"sha256(input)"," and returns the digest. This makes every function idempotent with respect to hashing, you can pass either form without breaking.",[856,1316,1317,1318,1140,1320,1323,1324,1327],{},"After ",[860,1319,1281],{},[860,1321,1322],{},"ensureSha256Hex"," runs a strict regex check (",[860,1325,1326],{},"\u002F^[a-f0-9]{64}$\u002Fi",") and throws if the value is malformed. This double gate prevents truncated or corrupted hashes from reaching the database.",[888,1329],{},[891,1331,1333],{"id":1332},"verification","Verification",[856,1335,1336],{},"The service exposes two verification functions with very different semantics.",[1338,1339,1341],"h3",{"id":1340},"verifyrefreshtoken",[860,1342,1343],{},"verifyRefreshToken",[856,1345,1346,1347,1349],{},"Read-only verification. Does not change ",[860,1348,877],{},". Suitable for quick checks where you plan to revoke the token yourself immediately after.",[1078,1351,1353],{"className":1080,"code":1352,"language":1082,"meta":1083,"style":1083},"import { verifyRefreshToken } from '@riavzon\u002Fauth'\n\nconst result = await verifyRefreshToken(clientToken)\n\nif (result.valid) {\n  const { userId, visitor_id, sessionStartedAt, expiresAt } = result\n}\n",[860,1354,1355,1373,1377,1400,1404,1422,1452],{"__ignoreMap":1083},[1087,1356,1357,1359,1361,1363,1365,1367,1369,1371],{"class":1089,"line":1090},[1087,1358,1094],{"class":1093},[1087,1360,1098],{"class":1097},[1087,1362,1343],{"class":1101},[1087,1364,1104],{"class":1097},[1087,1366,1107],{"class":1093},[1087,1368,1111],{"class":1110},[1087,1370,1115],{"class":1114},[1087,1372,1118],{"class":1110},[1087,1374,1375],{"class":1089,"line":1121},[1087,1376,1124],{"emptyLinePlaceholder":8},[1087,1378,1379,1381,1384,1387,1389,1392,1395,1398],{"class":1089,"line":1127},[1087,1380,1131],{"class":1130},[1087,1382,1383],{"class":1136}," result",[1087,1385,1386],{"class":1147}," =",[1087,1388,1151],{"class":1093},[1087,1390,1391],{"class":1154}," verifyRefreshToken",[1087,1393,1394],{"class":1097},"(",[1087,1396,1397],{"class":1101},"clientToken",[1087,1399,1209],{"class":1097},[1087,1401,1402],{"class":1089,"line":1161},[1087,1403,1124],{"emptyLinePlaceholder":8},[1087,1405,1406,1409,1412,1415,1417,1419],{"class":1089,"line":1184},[1087,1407,1408],{"class":1093},"if",[1087,1410,1411],{"class":1097}," (",[1087,1413,1414],{"class":1101},"result",[1087,1416,1167],{"class":1097},[1087,1418,983],{"class":1101},[1087,1420,1421],{"class":1097},") {\n",[1087,1423,1424,1427,1429,1431,1433,1436,1438,1441,1443,1445,1447,1449],{"class":1089,"line":1196},[1087,1425,1426],{"class":1130},"  const",[1087,1428,1098],{"class":1097},[1087,1430,1246],{"class":1136},[1087,1432,1140],{"class":1097},[1087,1434,1435],{"class":1136},"visitor_id",[1087,1437,1140],{"class":1097},[1087,1439,1440],{"class":1136},"sessionStartedAt",[1087,1442,1140],{"class":1097},[1087,1444,1013],{"class":1136},[1087,1446,1104],{"class":1097},[1087,1448,1148],{"class":1147},[1087,1450,1451],{"class":1101}," result\n",[1087,1453,1454],{"class":1089,"line":1206},[1087,1455,1456],{"class":1097},"}\n",[906,1458,1459,1471],{},[909,1460,1461],{},[912,1462,1463,1466,1468],{},[915,1464,1465],{},"Field",[915,1467,920],{},[915,1469,1470],{},"Present when",[925,1472,1473,1487,1500,1514,1526,1538],{},[912,1474,1475,1479,1484],{},[930,1476,1477],{},[860,1478,983],{},[930,1480,1481],{},[860,1482,1483],{},"boolean",[930,1485,1486],{},"Always",[912,1488,1489,1493,1497],{},[930,1490,1491],{},[860,1492,1246],{},[930,1494,1495],{},[860,1496,1236],{},[930,1498,1499],{},"Valid or expired",[912,1501,1502,1506,1511],{},[930,1503,1504],{},[860,1505,1435],{},[930,1507,1508],{},[860,1509,1510],{},"string",[930,1512,1513],{},"Valid",[912,1515,1516,1520,1524],{},[930,1517,1518],{},[860,1519,1440],{},[930,1521,1522],{},[860,1523,1265],{},[930,1525,1513],{},[912,1527,1528,1532,1536],{},[930,1529,1530],{},[860,1531,1013],{},[930,1533,1534],{},[860,1535,1265],{},[930,1537,1513],{},[912,1539,1540,1545,1549],{},[930,1541,1542],{},[860,1543,1544],{},"reason",[930,1546,1547],{},[860,1548,1510],{},[930,1550,1551],{},"Invalid",[856,1553,1554,1556],{},[860,1555,1343],{}," is not side-effect free. Inside a transaction, it:",[1558,1559,1560,1568],"ul",{},[1561,1562,1563,1564,1567],"li",{},"Deletes rows where ",[860,1565,1566],{},"valid = 0"," when a revoked token is presented",[1561,1569,1570,1571,1573,1574,1577,1578,1581],{},"Marks expired tokens as ",[860,1572,1566],{}," and sets the user's ",[860,1575,1576],{},"last_mfa_at"," to ",[860,1579,1580],{},"NULL"," (forces MFA on next login)",[1583,1584,1585],"warning",{},[856,1586,1587,1589,1590,1593],{},[860,1588,1343],{}," is not recommended for production consumption paths. It exists for convenience and quick verification. Use ",[860,1591,1592],{},"consumeAndVerifyRefreshToken"," for any path where the token should be invalidated after use.",[1338,1595,1597],{"id":1596},"consumeandverifyrefreshtoken",[860,1598,1592],{},[856,1600,1601],{},"Atomic verification and consumption. This is the function used by the rotation and logout controllers.",[1078,1603,1605],{"className":1080,"code":1604,"language":1082,"meta":1083,"style":1083},"import { consumeAndVerifyRefreshToken } from '@riavzon\u002Fauth'\n\nconst result = await consumeAndVerifyRefreshToken(clientToken)\n\nif (!result.valid) {\n  \u002F\u002F result.reason describes the failure\n}\n",[860,1606,1607,1625,1629,1648,1652,1669,1674],{"__ignoreMap":1083},[1087,1608,1609,1611,1613,1615,1617,1619,1621,1623],{"class":1089,"line":1090},[1087,1610,1094],{"class":1093},[1087,1612,1098],{"class":1097},[1087,1614,1592],{"class":1101},[1087,1616,1104],{"class":1097},[1087,1618,1107],{"class":1093},[1087,1620,1111],{"class":1110},[1087,1622,1115],{"class":1114},[1087,1624,1118],{"class":1110},[1087,1626,1627],{"class":1089,"line":1121},[1087,1628,1124],{"emptyLinePlaceholder":8},[1087,1630,1631,1633,1635,1637,1639,1642,1644,1646],{"class":1089,"line":1127},[1087,1632,1131],{"class":1130},[1087,1634,1383],{"class":1136},[1087,1636,1386],{"class":1147},[1087,1638,1151],{"class":1093},[1087,1640,1641],{"class":1154}," consumeAndVerifyRefreshToken",[1087,1643,1394],{"class":1097},[1087,1645,1397],{"class":1101},[1087,1647,1209],{"class":1097},[1087,1649,1650],{"class":1089,"line":1161},[1087,1651,1124],{"emptyLinePlaceholder":8},[1087,1653,1654,1656,1658,1661,1663,1665,1667],{"class":1089,"line":1184},[1087,1655,1408],{"class":1093},[1087,1657,1411],{"class":1097},[1087,1659,1660],{"class":1147},"!",[1087,1662,1414],{"class":1101},[1087,1664,1167],{"class":1097},[1087,1666,983],{"class":1101},[1087,1668,1421],{"class":1097},[1087,1670,1671],{"class":1089,"line":1196},[1087,1672,1673],{"class":1202},"  \u002F\u002F result.reason describes the failure\n",[1087,1675,1676],{"class":1089,"line":1206},[1087,1677,1456],{"class":1097},[906,1679,1680,1690],{},[909,1681,1682],{},[912,1683,1684,1686,1688],{},[915,1685,1465],{},[915,1687,920],{},[915,1689,1470],{},[925,1691,1692,1704,1716,1728,1745],{},[912,1693,1694,1698,1702],{},[930,1695,1696],{},[860,1697,983],{},[930,1699,1700],{},[860,1701,1483],{},[930,1703,1486],{},[912,1705,1706,1710,1714],{},[930,1707,1708],{},[860,1709,1246],{},[930,1711,1712],{},[860,1713,1236],{},[930,1715,1499],{},[912,1717,1718,1722,1726],{},[930,1719,1720],{},[860,1721,1435],{},[930,1723,1724],{},[860,1725,1510],{},[930,1727,1513],{},[912,1729,1730,1735,1739],{},[930,1731,1732],{},[860,1733,1734],{},"sessionTTL",[930,1736,1737],{},[860,1738,1265],{},[930,1740,1741,1742,1744],{},"Valid (the ",[860,1743,1050],{}," value)",[912,1746,1747,1751,1755],{},[930,1748,1749],{},[860,1750,1544],{},[930,1752,1753],{},[860,1754,1510],{},[930,1756,1551],{},[856,1758,1759,1760,1763,1764,1766,1767,1770],{},"The consumption happens in a single atomic ",[860,1761,1762],{},"UPDATE"," inside a transaction. It increments ",[860,1765,877],{}," by one, but only if the token exists, is still valid, has never been consumed (",[860,1768,1769],{},"usage_count = 0","), and has not expired. All four conditions must pass for the row to be affected.",[856,1772,1773,1774,1777],{},"If no row was affected, the function investigates why by selecting the row with ",[860,1775,1776],{},"FOR UPDATE",":",[1779,1780,1782,1787,1793,1797,1806,1810],"steps",{"level":1781},"4",[1783,1784,1786],"h4",{"id":1785},"token-not-found","Token not found",[856,1788,1789,1790,1167],{},"No row in the table. Returns ",[860,1791,1792],{},"{ valid: false, reason: 'Token not found' }",[1783,1794,1796],{"id":1795},"token-revoked","Token revoked",[856,1798,1799,1800,1802,1803,1167],{},"Row exists but ",[860,1801,1566],{},". Returns ",[860,1804,1805],{},"{ valid: false, reason: 'Token has been revoked' }",[1783,1807,1809],{"id":1808},"reuse-detected","Reuse detected",[856,1811,1812,1813,1815,1816,1819,1820,1824,1825,1167],{},"Row exists, ",[860,1814,1285],{},", but ",[860,1817,1818],{},"usage_count > 0",". This means someone already consumed this token. The function immediately revokes ",[1821,1822,1823],"strong",{},"all"," refresh tokens for that user and returns ",[860,1826,1827],{},"{ valid: false, reason: 'Token already used, Please login again' }",[856,1829,1830,1831,1834,1835,1838,1839,1841,1842,1844],{},"If ",[860,1832,1833],{},"affectedRows === 1",", the token was successfully consumed. The function then fetches the full row (joining ",[860,1836,1837],{},"users"," for ",[860,1840,1435],{},"), runs the same expired and revoked checks as ",[860,1843,1343],{},", and returns the valid result.",[888,1846],{},[891,1848,191],{"id":1849},"revocation",[1338,1851,1853],{"id":1852},"single-token","Single token",[1078,1855,1857],{"className":1080,"code":1856,"language":1082,"meta":1083,"style":1083},"import { revokeRefreshToken } from '@riavzon\u002Fauth'\n\nconst { success } = await revokeRefreshToken(clientToken)\n",[860,1858,1859,1878,1882],{"__ignoreMap":1083},[1087,1860,1861,1863,1865,1868,1870,1872,1874,1876],{"class":1089,"line":1090},[1087,1862,1094],{"class":1093},[1087,1864,1098],{"class":1097},[1087,1866,1867],{"class":1101},"revokeRefreshToken",[1087,1869,1104],{"class":1097},[1087,1871,1107],{"class":1093},[1087,1873,1111],{"class":1110},[1087,1875,1115],{"class":1114},[1087,1877,1118],{"class":1110},[1087,1879,1880],{"class":1089,"line":1121},[1087,1881,1124],{"emptyLinePlaceholder":8},[1087,1883,1884,1886,1888,1891,1893,1895,1897,1900,1902,1904],{"class":1089,"line":1127},[1087,1885,1131],{"class":1130},[1087,1887,1098],{"class":1097},[1087,1889,1890],{"class":1136},"success",[1087,1892,1104],{"class":1097},[1087,1894,1148],{"class":1147},[1087,1896,1151],{"class":1093},[1087,1898,1899],{"class":1154}," revokeRefreshToken",[1087,1901,1394],{"class":1097},[1087,1903,1397],{"class":1101},[1087,1905,1209],{"class":1097},[856,1907,1908,1909,1911],{},"Sets ",[860,1910,1566],{}," on the matching row. Does not delete the row, the row stays around so reuse detection can still identify it later.",[1338,1913,1915],{"id":1914},"all-user-sessions","All user sessions",[1078,1917,1919],{"className":1080,"code":1918,"language":1082,"meta":1083,"style":1083},"import { revokeAllRefreshTokens } from '@riavzon\u002Fauth'\n\nconst { success } = await revokeAllRefreshTokens(userId)\n",[860,1920,1921,1940,1944],{"__ignoreMap":1083},[1087,1922,1923,1925,1927,1930,1932,1934,1936,1938],{"class":1089,"line":1090},[1087,1924,1094],{"class":1093},[1087,1926,1098],{"class":1097},[1087,1928,1929],{"class":1101},"revokeAllRefreshTokens",[1087,1931,1104],{"class":1097},[1087,1933,1107],{"class":1093},[1087,1935,1111],{"class":1110},[1087,1937,1115],{"class":1114},[1087,1939,1118],{"class":1110},[1087,1941,1942],{"class":1089,"line":1121},[1087,1943,1124],{"emptyLinePlaceholder":8},[1087,1945,1946,1948,1950,1952,1954,1956,1958,1961,1963,1965],{"class":1089,"line":1127},[1087,1947,1131],{"class":1130},[1087,1949,1098],{"class":1097},[1087,1951,1890],{"class":1136},[1087,1953,1104],{"class":1097},[1087,1955,1148],{"class":1147},[1087,1957,1151],{"class":1093},[1087,1959,1960],{"class":1154}," revokeAllRefreshTokens",[1087,1962,1394],{"class":1097},[1087,1964,1246],{"class":1101},[1087,1966,1209],{"class":1097},[856,1968,1969,1970,1972],{},"Marks every valid refresh token for the given user as invalid. Used by the reuse detection path inside ",[860,1971,1592],{}," and exposed for manual use when you need to force-logout a user from all devices.",[888,1974],{},[891,1976,195],{"id":1977},"rotation",[856,1979,1980],{},"Rotation is the process of consuming the current refresh token and issuing a new one. The service provides two rotation helpers for library users and one route for standalone service users.",[1338,1982,1984],{"id":1983},"rotateoneuserefreshtoken",[860,1985,1986],{},"rotateOneUseRefreshToken",[856,1988,1989],{},"Verifies the old token, revokes it, then generates a fresh token with a new row.",[1078,1991,1993],{"className":1080,"code":1992,"language":1082,"meta":1083,"style":1083},"import { rotateOneUseRefreshToken } from '@riavzon\u002Fauth'\n\nconst result = await rotateOneUseRefreshToken(\n  config.jwt.refresh_tokens.refresh_ttl,\n  user.id,\n  oldRawToken\n)\n\nif (result.rotated) {\n  \u002F\u002F result.raw = new token, result.expiresAt = new expiry\n}\n",[860,1994,1995,2013,2017,2032,2050,2060,2065,2069,2074,2090,2096],{"__ignoreMap":1083},[1087,1996,1997,1999,2001,2003,2005,2007,2009,2011],{"class":1089,"line":1090},[1087,1998,1094],{"class":1093},[1087,2000,1098],{"class":1097},[1087,2002,1986],{"class":1101},[1087,2004,1104],{"class":1097},[1087,2006,1107],{"class":1093},[1087,2008,1111],{"class":1110},[1087,2010,1115],{"class":1114},[1087,2012,1118],{"class":1110},[1087,2014,2015],{"class":1089,"line":1121},[1087,2016,1124],{"emptyLinePlaceholder":8},[1087,2018,2019,2021,2023,2025,2027,2030],{"class":1089,"line":1127},[1087,2020,1131],{"class":1130},[1087,2022,1383],{"class":1136},[1087,2024,1386],{"class":1147},[1087,2026,1151],{"class":1093},[1087,2028,2029],{"class":1154}," rotateOneUseRefreshToken",[1087,2031,1158],{"class":1097},[1087,2033,2034,2036,2038,2040,2042,2044,2046,2048],{"class":1089,"line":1161},[1087,2035,1164],{"class":1101},[1087,2037,1167],{"class":1097},[1087,2039,1170],{"class":1101},[1087,2041,1167],{"class":1097},[1087,2043,862],{"class":1101},[1087,2045,1167],{"class":1097},[1087,2047,1023],{"class":1101},[1087,2049,1181],{"class":1097},[1087,2051,2052,2054,2056,2058],{"class":1089,"line":1184},[1087,2053,1187],{"class":1101},[1087,2055,1167],{"class":1097},[1087,2057,934],{"class":1101},[1087,2059,1181],{"class":1097},[1087,2061,2062],{"class":1089,"line":1196},[1087,2063,2064],{"class":1101},"  oldRawToken\n",[1087,2066,2067],{"class":1089,"line":1206},[1087,2068,1209],{"class":1097},[1087,2070,2072],{"class":1089,"line":2071},8,[1087,2073,1124],{"emptyLinePlaceholder":8},[1087,2075,2077,2079,2081,2083,2085,2088],{"class":1089,"line":2076},9,[1087,2078,1408],{"class":1093},[1087,2080,1411],{"class":1097},[1087,2082,1414],{"class":1101},[1087,2084,1167],{"class":1097},[1087,2086,2087],{"class":1101},"rotated",[1087,2089,1421],{"class":1097},[1087,2091,2093],{"class":1089,"line":2092},10,[1087,2094,2095],{"class":1202},"  \u002F\u002F result.raw = new token, result.expiresAt = new expiry\n",[1087,2097,2099],{"class":1089,"line":2098},11,[1087,2100,1456],{"class":1097},[856,2102,2103,2104,2106,2107,2109,2110,2112,2113,2115,2116,2118,2119,2121],{},"This helper calls ",[860,2105,1343],{}," (not ",[860,2108,1592],{},"), then ",[860,2111,1867],{},", then ",[860,2114,903],{},". It validates that the ",[860,2117,1246],{}," argument matches the token's ",[860,2120,949],{}," before proceeding.",[1338,2123,2125],{"id":2124},"rotateinplacerefreshtoken",[860,2126,2127],{},"rotateInPlaceRefreshToken",[856,2129,2130],{},"Updates an existing expired row in place rather than creating a new row. Only succeeds when the token is both invalid and expired.",[1078,2132,2134],{"className":1080,"code":2133,"language":1082,"meta":1083,"style":1083},"import { rotateInPlaceRefreshToken } from '@riavzon\u002Fauth'\n\nconst result = await rotateInPlaceRefreshToken(\n  config.jwt.refresh_tokens.refresh_ttl,\n  user.id,\n  oldRawToken\n)\n",[860,2135,2136,2154,2158,2173,2191,2201,2205],{"__ignoreMap":1083},[1087,2137,2138,2140,2142,2144,2146,2148,2150,2152],{"class":1089,"line":1090},[1087,2139,1094],{"class":1093},[1087,2141,1098],{"class":1097},[1087,2143,2127],{"class":1101},[1087,2145,1104],{"class":1097},[1087,2147,1107],{"class":1093},[1087,2149,1111],{"class":1110},[1087,2151,1115],{"class":1114},[1087,2153,1118],{"class":1110},[1087,2155,2156],{"class":1089,"line":1121},[1087,2157,1124],{"emptyLinePlaceholder":8},[1087,2159,2160,2162,2164,2166,2168,2171],{"class":1089,"line":1127},[1087,2161,1131],{"class":1130},[1087,2163,1383],{"class":1136},[1087,2165,1386],{"class":1147},[1087,2167,1151],{"class":1093},[1087,2169,2170],{"class":1154}," rotateInPlaceRefreshToken",[1087,2172,1158],{"class":1097},[1087,2174,2175,2177,2179,2181,2183,2185,2187,2189],{"class":1089,"line":1161},[1087,2176,1164],{"class":1101},[1087,2178,1167],{"class":1097},[1087,2180,1170],{"class":1101},[1087,2182,1167],{"class":1097},[1087,2184,862],{"class":1101},[1087,2186,1167],{"class":1097},[1087,2188,1023],{"class":1101},[1087,2190,1181],{"class":1097},[1087,2192,2193,2195,2197,2199],{"class":1089,"line":1184},[1087,2194,1187],{"class":1101},[1087,2196,1167],{"class":1097},[1087,2198,934],{"class":1101},[1087,2200,1181],{"class":1097},[1087,2202,2203],{"class":1089,"line":1196},[1087,2204,2064],{"class":1101},[1087,2206,2207],{"class":1089,"line":1206},[1087,2208,1209],{"class":1097},[856,2210,2211],{},"The function generates a new random token, hashes it, and replaces the old token hash, expiry, and validity flag on the existing row. The update only affects the row if it belongs to the specified user, is currently invalid, and has already expired. If all conditions are met, the row becomes valid again with a fresh token and a new TTL.",[1583,2213,2214],{},[856,2215,2216,2217,2219],{},"In-place rotation relaxes the stricter model where expired tokens always require a new row. Make sure higher-level logic still enforces ",[860,2218,1060],{},", anomaly checks, and MFA before calling this function.",[2221,2222,2223],"caution",{},[856,2224,2225,2226,2228,2229,2231],{},"Be Very carful how you use ",[860,2227,2127],{},", and ",[860,2230,1986],{}," it completely bypass the reuse detection.\nBoth of these 2 not used in the life-cycles of the sessions.",[1338,2233,2235],{"id":2234},"the-rotation-route","The Rotation Route",[856,2237,2238,2239,2242],{},"Standalone service users call ",[860,2240,2241],{},"POST \u002Fauth\u002Fuser\u002Frefresh-session"," to rotate. The middleware chain is:",[1078,2244,2249],{"className":2245,"code":2247,"language":2248},[2246],"language-text","requireRefreshToken, acceptCookieOnly, getFingerPrint, checkForActiveMfa rotateCredentials\n","text",[860,2250,2247],{"__ignoreMap":1083},[856,2252,2253,2256],{},[860,2254,2255],{},"rotateCredentials"," is the controller. It runs in this order:",[1779,2258,2259,2263,2274,2278,2304,2308,2329,2333,2341,2345],{"level":1781},[1783,2260,2262],{"id":2261},"rate-limiting","Rate limiting",[856,2264,2265,2266,2269,2270,2273],{},"Guards against brute force with three limiters layered: IP limiter, token hash limiter, and a composite ",[860,2267,2268],{},"ip_tokenhash"," limiter. Each uses ",[860,2271,2272],{},"guard()"," with consecutive caches that escalate block duration on repeated violation.",[1783,2275,2277],{"id":2276},"anomaly-detection","Anomaly detection",[856,2279,2280,2281,2284,2285,2288,2289,2292,2293,2295,2296,2299,2300,2303],{},"Calls ",[860,2282,2283],{},"strangeThings(rawRefreshToken, canary_id, ip, userAgent, true)"," with ",[860,2286,2287],{},"rotated = true",". The ",[860,2290,2291],{},"true"," flag tells the anomaly engine to also check ",[860,2294,1818],{}," as a hard rejection. If anomalies are detected, the engine either triggers MFA (",[860,2297,2298],{},"202",") or demands re-login (",[860,2301,2302],{},"401",").",[1783,2305,2307],{"id":2306},"consume-the-old-token","Consume the old token",[856,2309,2280,2310,2313,2314,2316,2317,2319,2320,1286,2322,2325,2326,1167],{},[860,2311,2312],{},"consumeAndVerifyRefreshToken(rawRefreshToken)",". If the token is valid but ",[860,2315,1050],{}," is older than ",[860,2318,1060],{},", the controller revokes it, clears both ",[860,2321,870],{},[860,2323,2324],{},"iat"," cookies, and returns ",[860,2327,2328],{},"401 Session is expired",[1783,2330,2332],{"id":2331},"revoke-the-old-token","Revoke the old token",[856,2334,2280,2335,2338,2339,1167],{},[860,2336,2337],{},"revokeRefreshToken(rawRefreshToken)"," to set ",[860,2340,1566],{},[1783,2342,2344],{"id":2343},"issue-new-credentials","Issue new credentials",[856,2346,2347,2348,2351,2352,2355,2356,2358,2359,2361,2362,2364,2365,2367,2368,2371],{},"Generates a new refresh token with ",[860,2349,2350],{},"generateRefreshToken(refresh_ttl, userId, sessionTTL)"," and a new access token with ",[860,2353,2354],{},"generateAccessToken",", the ",[860,2357,1734],{}," comes from the result of ",[860,2360,2312],{},". Sets the ",[860,2363,870],{}," cookie (with the new refresh token) and the ",[860,2366,2324],{}," cookie (with ",[860,2369,2370],{},"Date.now()","). Blocks the old token hash in the rate limiter for 3 days.",[856,2373,2374,2375,2378],{},"Success response (",[860,2376,2377],{},"201","):",[1078,2380,2383],{"className":2381,"code":2382,"language":5,"meta":1083,"style":1083},"language-json shiki shiki-themes light-plus light-plus dracula","{\n  \"message\": \"Refresh & access tokens rotated\",\n  \"accessToken\": \"\u003Csigned jwt>\",\n  \"accessIat\": \"1710000000000\"\n}\n",[860,2384,2385,2390,2415,2435,2454],{"__ignoreMap":1083},[1087,2386,2387],{"class":1089,"line":1090},[1087,2388,2389],{"class":1097},"{\n",[1087,2391,2392,2396,2400,2403,2405,2408,2411,2413],{"class":1089,"line":1121},[1087,2393,2395],{"class":2394},"saJyd","  \"",[1087,2397,2399],{"class":2398},"s_W10","message",[1087,2401,2402],{"class":2394},"\"",[1087,2404,1777],{"class":1147},[1087,2406,2407],{"class":1110}," \"",[1087,2409,2410],{"class":1114},"Refresh & access tokens rotated",[1087,2412,2402],{"class":1110},[1087,2414,1181],{"class":1097},[1087,2416,2417,2419,2422,2424,2426,2428,2431,2433],{"class":1089,"line":1127},[1087,2418,2395],{"class":2394},[1087,2420,2421],{"class":2398},"accessToken",[1087,2423,2402],{"class":2394},[1087,2425,1777],{"class":1147},[1087,2427,2407],{"class":1110},[1087,2429,2430],{"class":1114},"\u003Csigned jwt>",[1087,2432,2402],{"class":1110},[1087,2434,1181],{"class":1097},[1087,2436,2437,2439,2442,2444,2446,2448,2451],{"class":1089,"line":1161},[1087,2438,2395],{"class":2394},[1087,2440,2441],{"class":2398},"accessIat",[1087,2443,2402],{"class":2394},[1087,2445,1777],{"class":1147},[1087,2447,2407],{"class":1110},[1087,2449,2450],{"class":1114},"1710000000000",[1087,2452,2453],{"class":1110},"\"\n",[1087,2455,2456],{"class":1089,"line":1184},[1087,2457,1456],{"class":1097},[888,2459],{},[891,2461,107],{"id":2462},"logout",[856,2464,2238,2465,2468],{},[860,2466,2467],{},"POST \u002Fauth\u002Flogout",". The middleware chain is:",[1078,2470,2473],{"className":2471,"code":2472,"language":2248},[2246],"requireRefreshToken, requireAccessToken, acceptCookieOnly, handleLogout\n",[860,2474,2472],{"__ignoreMap":1083},[856,2476,2477],{},"The logout controller:",[2479,2480,2481,2487,2496,2502,2509],"ol",{},[1561,2482,2483,2484,2486],{},"Consumes the refresh token with ",[860,2485,1592],{}," (atomic, prevents post-logout reuse)",[1561,2488,2489,2490,2492,2493,2495],{},"Revokes it with ",[860,2491,1867],{}," (sets ",[860,2494,1566],{},")",[1561,2497,2498,2499,2501],{},"Blocks the token hash in the rate limiter for the remaining ",[860,2500,1023],{}," duration",[1561,2503,2504,2505,2508],{},"Verifies the access token and blacklists its ",[860,2506,2507],{},"jti"," for 24 hours",[1561,2510,2511,2512,1286,2514,2516],{},"Clears the ",[860,2513,870],{},[860,2515,2324],{}," cookies",[856,2518,2374,2519,2378],{},[860,2520,2521],{},"200",[1078,2523,2525],{"className":2381,"code":2524,"language":5,"meta":1083,"style":1083},"{ \"ok\": true, \"message\": \"Logged out successfully\" }\n",[860,2526,2527],{"__ignoreMap":1083},[1087,2528,2529,2532,2534,2537,2539,2541,2545,2547,2549,2551,2553,2555,2557,2560,2562],{"class":1089,"line":1090},[1087,2530,2531],{"class":1097},"{ ",[1087,2533,2402],{"class":2394},[1087,2535,2536],{"class":2398},"ok",[1087,2538,2402],{"class":2394},[1087,2540,1777],{"class":1147},[1087,2542,2544],{"class":2543},"sjR7W"," true",[1087,2546,1140],{"class":1097},[1087,2548,2402],{"class":2394},[1087,2550,2399],{"class":2398},[1087,2552,2402],{"class":2394},[1087,2554,1777],{"class":1147},[1087,2556,2407],{"class":1110},[1087,2558,2559],{"class":1114},"Logged out successfully",[1087,2561,2402],{"class":1110},[1087,2563,2564],{"class":1097}," }\n",[888,2566],{},[891,2568,2570],{"id":2569},"reuse-detection","Reuse Detection",[856,2572,2573,2574,2576,2577,2579,2580,2582],{},"The reuse detection system relies on two mechanisms working together: the ",[860,2575,877],{}," column in ",[860,2578,1592],{},", and the anomaly engine's ",[860,2581,2087],{}," flag check.",[1338,2584,2586,2587],{"id":2585},"through-consumeandverifyrefreshtoken","Through ",[860,2588,1592],{},[856,2590,2591,2592,2594,2595,2597,2598,2601],{},"When a token has ",[860,2593,1818],{}," and someone tries to consume it again, the function assumes the token family is compromised. It revokes ",[1821,2596,1823],{}," valid refresh tokens for that user and returns ",[860,2599,2600],{},"valid: false",". Both the attacker and the legitimate user are forced to re-authenticate.",[1338,2603,2605],{"id":2604},"through-the-anomaly-engine","Through the anomaly engine",[856,2607,2608,2609,2612,2613,2615,2616,2618,2619,2622,2623,2625],{},"When ",[860,2610,2611],{},"strangeThings"," runs with ",[860,2614,2287],{}," (the rotation route), it checks ",[860,2617,1818],{}," as a hard rejection alongside its other checks. If the token was already consumed, the anomaly engine returns ",[860,2620,2621],{},"valid: false, reqMFA: false",", which the rotation controller translates to a ",[860,2624,2302],{}," re-login response.",[1338,2627,2629],{"id":2628},"scenario-walkthrough","Scenario walkthrough",[856,2631,2632],{},[1821,2633,2634],{},"An attacker obtains a valid, unconsumed refresh token.",[856,2636,2637,2638,2641,2642,2644,2645,2647,2648,2650],{},"The attacker needs to replicate the user's exact fingerprint and ",[860,2639,2640],{},"canary_id"," cookie to avoid triggering MFA. If the fingerprint does not match, the anomaly engine flags it and sends an MFA challenge to the real user's email. If the attacker somehow passes the fingerprint check and consumes the token (",[860,2643,877],{}," goes to ",[860,2646,885],{},"), the legitimate user's next rotation attempt hits ",[860,2649,1818],{}," and all sessions are revoked.",[856,2652,2653],{},[1821,2654,2655],{},"An attacker obtains a previously rotated (stale) token.",[856,2657,2658,2659,2662],{},"The token already has ",[860,2660,2661],{},"usage_count >= 1",". The attacker's attempt to consume it immediately triggers the revoke-all path. The legitimate user's current valid token is also invalidated. Both parties must re-authenticate, and the attacker cannot complete MFA without access to the user's email.",[888,2664],{},[891,2666,2668],{"id":2667},"session-lifetime","Session Lifetime",[856,2670,2671],{},"Refresh tokens have two independent lifetime controls:",[856,2673,2674,2679,2680,2682,2683,1286,2685,2687,2688,2690,2691,1577,2693,2695,2696,2699],{},[1821,2675,2676,2677,2495],{},"Token TTL (",[860,2678,1023],{},"\nSet in milliseconds in the configuration. Controls the ",[860,2681,1013],{}," column on each row. When a token expires, both ",[860,2684,1343],{},[860,2686,1592],{}," mark it ",[860,2689,1566],{}," and set the user's ",[860,2692,1576],{},[860,2694,1580],{},". This resets the ",[860,2697,2698],{},"byPassAnomaliesFor"," cooldown window, meaning the next time the user hits any anomaly condition (too many sessions, IP mismatch, canary mismatch, etc.) it will not be bypassed. A clean login from the same device with no anomalies still passes without MFA.",[856,2701,2702,2707,2708,2711,2712,2714,2715,2717],{},[1821,2703,2704,2705,2495],{},"Session lifetime (",[860,2706,1060],{},"\nAlso in milliseconds. The rotation controller compares ",[860,2709,2710],{},"Date.now() - session_started_at"," against this value. If the session has been alive longer than ",[860,2713,1060],{},", the controller revokes the token and returns ",[860,2716,2328],{},", even if the token itself has not expired yet.",[856,2719,2720,2721,2723,2724,2726],{},"The distinction matters: ",[860,2722,1023],{}," controls how long a single token lives. ",[860,2725,1060],{}," controls how long the session chain (original token plus all its rotated successors) can stay alive, both controlled in the configuration.",[888,2728],{},[891,2730,2732],{"id":2731},"session-limits","Session Limits",[856,2734,2735,2736,1777],{},"The anomaly engine enforces two session-count rules, both configured under ",[860,2737,2738],{},"jwt.refresh_tokens",[856,2740,2741,2746],{},[1821,2742,2743],{},[860,2744,2745],{},"maxAllowedSessionsPerUser"," limits the number of concurrent valid sessions. When a user exceeds this count, the anomaly engine flags the next rotation or access-check as requiring MFA. The user must prove their identity before the service issues new tokens.",[856,2748,2749,2753,2754,2757],{},[1821,2750,2751],{},[860,2752,2698],{}," is a cooldown period in milliseconds. If the user completed MFA within that window (",[860,2755,2756],{},"last_mfa_at + byPassAnomaliesFor > now","), the session-count check is skipped. This prevents MFA spam when a user legitimately logs in from multiple devices in quick succession.",[856,2759,2760,2761,2303],{},"The anomaly engine also flags rapid token creation: if more than 3 valid tokens were created in the last 10 minutes for the same user, the presented token is revoked and the session is terminated without MFA (hard ",[860,2762,2302],{},[856,2764,2765,2766],{},"More details at ",[2767,2768,2770],"a",{"href":2769},"\u002Fdocs\u002Fiam\u002Fanomalies","anomalies",[888,2772],{},[891,2774,2776],{"id":2775},"configuration-reference","Configuration Reference",[856,2778,2779,2780,2782,2783,1167],{},"All refresh token options live under ",[860,2781,2738],{}," in the object passed to ",[860,2784,2785],{},"configuration()",[906,2787,2788,2799],{},[909,2789,2790],{},[912,2791,2792,2795,2797],{},[915,2793,2794],{},"Option",[915,2796,920],{},[915,2798,923],{},[925,2800,2801,2814,2834,2850,2863],{},[912,2802,2803,2807,2811],{},[930,2804,2805],{},[860,2806,1023],{},[930,2808,2809],{},[860,2810,1236],{},[930,2812,2813],{},"Token lifetime in milliseconds. Used as the TTL for newly generated tokens",[912,2815,2816,2821,2825],{},[930,2817,2818],{},[860,2819,2820],{},"domain",[930,2822,2823],{},[860,2824,1510],{},[930,2826,2827,2828,2830,2831,2495],{},"Cookie domain for the ",[860,2829,870],{}," cookie (e.g. ",[860,2832,2833],{},"example.com",[912,2835,2836,2840,2844],{},[930,2837,2838],{},[860,2839,1060],{},[930,2841,2842],{},[860,2843,1236],{},[930,2845,2846,2847,2849],{},"Maximum session chain lifetime in milliseconds. Rotation is rejected when the original ",[860,2848,1050],{}," is older than this",[912,2851,2852,2856,2860],{},[930,2853,2854],{},[860,2855,2745],{},[930,2857,2858],{},[860,2859,1236],{},[930,2861,2862],{},"Maximum concurrent valid refresh tokens per user before MFA is triggered",[912,2864,2865,2869,2873],{},[930,2866,2867],{},[860,2868,2698],{},[930,2870,2871],{},[860,2872,1236],{},[930,2874,2875,2876,2878],{},"MFA cooldown in milliseconds. Session-count anomalies are skipped if ",[860,2877,1576],{}," is within this window",[2880,2881,2882],"note",{},[856,2883,2884,1286,2886,2888,2889,2891,2892,2894,2895,2897,2898,2900],{},[860,2885,1023],{},[860,2887,1060],{}," serve different purposes. ",[860,2890,1023],{}," is how long one token lives. ",[860,2893,1060],{}," is how long the entire session chain can survive across rotations. Set ",[860,2896,1060],{}," to something like 7 or 30 days depending on your security posture, and ",[860,2899,1023],{}," to something shorter (e.g. 3 days). This forces periodic rotation while still capping absolute session duration.",[2902,2903,2904],"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 .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 .saJyd, html code.shiki .saJyd{--shiki-light:#0451A5;--shiki-default:#0451A5;--shiki-dark:#8BE9FE}html pre.shiki code .s_W10, html code.shiki .s_W10{--shiki-light:#0451A5;--shiki-default:#0451A5;--shiki-dark:#8BE9FD}html pre.shiki code .sjR7W, html code.shiki .sjR7W{--shiki-light:#0000FF;--shiki-default:#0000FF;--shiki-dark:#BD93F9}",{"title":1083,"searchDepth":1121,"depth":1121,"links":2906},[2907,2908,2909,2910,2914,2918,2923,2924,2930,2931,2932],{"id":893,"depth":1121,"text":894},{"id":1075,"depth":1121,"text":1076},{"id":1303,"depth":1121,"text":1304},{"id":1332,"depth":1121,"text":1333,"children":2911},[2912,2913],{"id":1340,"depth":1127,"text":1343},{"id":1596,"depth":1127,"text":1592},{"id":1849,"depth":1121,"text":191,"children":2915},[2916,2917],{"id":1852,"depth":1127,"text":1853},{"id":1914,"depth":1127,"text":1915},{"id":1977,"depth":1121,"text":195,"children":2919},[2920,2921,2922],{"id":1983,"depth":1127,"text":1986},{"id":2124,"depth":1127,"text":2127},{"id":2234,"depth":1127,"text":2235},{"id":2462,"depth":1121,"text":107},{"id":2569,"depth":1121,"text":2570,"children":2925},[2926,2928,2929],{"id":2585,"depth":1127,"text":2927},"Through consumeAndVerifyRefreshToken",{"id":2604,"depth":1127,"text":2605},{"id":2628,"depth":1127,"text":2629},{"id":2667,"depth":1121,"text":2668},{"id":2731,"depth":1121,"text":2732},{"id":2775,"depth":1121,"text":2776},"How the IAM service generates, stores, verifies, consumes, rotates, and revokes refresh tokens.","md","i-lucide-rotate-cw",{},null,"---\ntitle: Refresh Tokens\ndescription: How the IAM service generates, stores, verifies, consumes, rotates, and revokes refresh tokens.\nicon: i-lucide-rotate-cw\n---\n\nRefresh tokens are opaque random strings, 64 bytes, hex encoded. The service stores a SHA-256 hash of each token in a MySQL `refresh_tokens` table alongside usage metadata. The raw token is sent to the client exactly once, as an `httpOnly` cookie named `session`, and never stored in plaintext on the server.\n\nEvery refresh token has a `usage_count` column. That column is the core of the reuse detection system. A token starts at `0`, gets atomically changed to `1` on consumption, and any second attempt with the same token revokes every active session for that user.\n\n---\n\n## The Database Row\n\nEach token maps to one row in the `refresh_tokens` table. Here is what gets stored when `generateRefreshToken` runs:\n\n| Column | Type | Description |\n|---|---|---|\n| `id` | `INT AUTO_INCREMENT` | Primary key |\n| `user_id` | `INT NOT NULL` | Foreign key to `users(id)`, cascades on delete |\n| `token` | `VARCHAR(600) UNIQUE` | SHA-256 hex hash of the raw token |\n| `valid` | `BOOLEAN DEFAULT 0` | Whether the token is still usable |\n| `created_at` | `TIMESTAMP` | When the row was inserted |\n| `expiresAt` | `TIMESTAMP` | Computed from `refresh_ttl` at insert time |\n| `usage_count` | `INT DEFAULT 0` | `0` = fresh, `1` = consumed, used by reuse detection |\n| `session_started_at` | `TIMESTAMP` | Set once at creation and carried forward to each new token on rotation. Used for `MAX_SESSION_LIFE` enforcement |\n\nThe `token` column is unique. The `user_id` foreign key cascades on delete, so deleting a user wipes all their sessions automatically.\n\n---\n\n## Generating a Token\n\n```ts\nimport { generateRefreshToken } from '@riavzon\u002Fauth'\n\nconst { raw, expiresAt } = await generateRefreshToken(\n  config.jwt.refresh_tokens.refresh_ttl,\n  user.id,\n  prevTs \u002F\u002F optional\n)\n```\n\n| Parameter | Type | Description |\n|---|---|---|\n| `ttl` | `number` | Lifetime in milliseconds |\n| `userId` | `number` | User ID to associate the token with |\n| `prevTs` | `Date` OR `undefined` | The previous timestamp of the session to set in the db, defaults to `UTC_TIMESTAMP()` |\n\nThe function generates 64 random bytes, hex encodes them, computes the SHA-256 hash with `toDigestHex`, then inserts a row with `valid = 1` and `expiresAt` computed from the provided TTL. It returns the raw unhashed token and the expiry date. That raw value is what goes into the `session` cookie. The server never sees it again after this point.\n\nWhen using the standalone service, the service generates and sends refresh tokens automatically on signup, login, MFA completion, and token rotation. You do not call `generateRefreshToken` directly in that mode.\n\n---\n\n## Hashing\n\nAll refresh token functions accept a raw token string and hash it internally before any database operation. The `toDigestHex` utility is a function that checks whether the input is already a SHA-256 hex string. If it is, it passes through. If not, it computes `sha256(input)` and returns the digest. This makes every function idempotent with respect to hashing, you can pass either form without breaking.\n\nAfter `toDigestHex`, `ensureSha256Hex` runs a strict regex check (`\u002F^[a-f0-9]{64}$\u002Fi`) and throws if the value is malformed. This double gate prevents truncated or corrupted hashes from reaching the database.\n\n---\n\n## Verification\n\nThe service exposes two verification functions with very different semantics.\n\n### `verifyRefreshToken`\n\nRead-only verification. Does not change `usage_count`. Suitable for quick checks where you plan to revoke the token yourself immediately after.\n\n```ts\nimport { verifyRefreshToken } from '@riavzon\u002Fauth'\n\nconst result = await verifyRefreshToken(clientToken)\n\nif (result.valid) {\n  const { userId, visitor_id, sessionStartedAt, expiresAt } = result\n}\n```\n\n| Field | Type | Present when |\n|---|---|---|\n| `valid` | `boolean` | Always |\n| `userId` | `number` | Valid or expired |\n| `visitor_id` | `string` | Valid |\n| `sessionStartedAt` | `Date` | Valid |\n| `expiresAt` | `Date` | Valid |\n| `reason` | `string` | Invalid |\n\n`verifyRefreshToken` is not side-effect free. Inside a transaction, it:\n- Deletes rows where `valid = 0` when a revoked token is presented\n- Marks expired tokens as `valid = 0` and sets the user's `last_mfa_at` to `NULL` (forces MFA on next login)\n\n::warning\n`verifyRefreshToken` is not recommended for production consumption paths. It exists for convenience and quick verification. Use `consumeAndVerifyRefreshToken` for any path where the token should be invalidated after use.\n::\n\n### `consumeAndVerifyRefreshToken`\n\nAtomic verification and consumption. This is the function used by the rotation and logout controllers.\n\n```ts\nimport { consumeAndVerifyRefreshToken } from '@riavzon\u002Fauth'\n\nconst result = await consumeAndVerifyRefreshToken(clientToken)\n\nif (!result.valid) {\n  \u002F\u002F result.reason describes the failure\n}\n```\n\n| Field | Type | Present when |\n|---|---|---|\n| `valid` | `boolean` | Always |\n| `userId` | `number` | Valid or expired |\n| `visitor_id` | `string` | Valid |\n| `sessionTTL` | `Date` | Valid (the `session_started_at` value) |\n| `reason` | `string` | Invalid |\n\nThe consumption happens in a single atomic `UPDATE` inside a transaction. It increments `usage_count` by one, but only if the token exists, is still valid, has never been consumed (`usage_count = 0`), and has not expired. All four conditions must pass for the row to be affected.\n\nIf no row was affected, the function investigates why by selecting the row with `FOR UPDATE`:\n\n::steps{level=\"4\"}\n#### Token not found\nNo row in the table. Returns `{ valid: false, reason: 'Token not found' }`.\n\n#### Token revoked\nRow exists but `valid = 0`. Returns `{ valid: false, reason: 'Token has been revoked' }`.\n\n#### Reuse detected\nRow exists, `valid = 1`, but `usage_count > 0`. This means someone already consumed this token. The function immediately revokes **all** refresh tokens for that user and returns `{ valid: false, reason: 'Token already used, Please login again' }`.\n::\n\nIf `affectedRows === 1`, the token was successfully consumed. The function then fetches the full row (joining `users` for `visitor_id`), runs the same expired and revoked checks as `verifyRefreshToken`, and returns the valid result.\n\n---\n\n## Revocation\n\n### Single token\n\n```ts\nimport { revokeRefreshToken } from '@riavzon\u002Fauth'\n\nconst { success } = await revokeRefreshToken(clientToken)\n```\n\nSets `valid = 0` on the matching row. Does not delete the row, the row stays around so reuse detection can still identify it later.\n\n### All user sessions\n\n```ts\nimport { revokeAllRefreshTokens } from '@riavzon\u002Fauth'\n\nconst { success } = await revokeAllRefreshTokens(userId)\n```\n\nMarks every valid refresh token for the given user as invalid. Used by the reuse detection path inside `consumeAndVerifyRefreshToken` and exposed for manual use when you need to force-logout a user from all devices.\n\n---\n\n## Rotation\n\nRotation is the process of consuming the current refresh token and issuing a new one. The service provides two rotation helpers for library users and one route for standalone service users.\n\n### `rotateOneUseRefreshToken`\n\nVerifies the old token, revokes it, then generates a fresh token with a new row.\n\n```ts\nimport { rotateOneUseRefreshToken } from '@riavzon\u002Fauth'\n\nconst result = await rotateOneUseRefreshToken(\n  config.jwt.refresh_tokens.refresh_ttl,\n  user.id,\n  oldRawToken\n)\n\nif (result.rotated) {\n  \u002F\u002F result.raw = new token, result.expiresAt = new expiry\n}\n```\n\nThis helper calls `verifyRefreshToken` (not `consumeAndVerifyRefreshToken`), then `revokeRefreshToken`, then `generateRefreshToken`. It validates that the `userId` argument matches the token's `user_id` before proceeding.\n\n### `rotateInPlaceRefreshToken`\n\nUpdates an existing expired row in place rather than creating a new row. Only succeeds when the token is both invalid and expired.\n\n```ts\nimport { rotateInPlaceRefreshToken } from '@riavzon\u002Fauth'\n\nconst result = await rotateInPlaceRefreshToken(\n  config.jwt.refresh_tokens.refresh_ttl,\n  user.id,\n  oldRawToken\n)\n```\n\nThe function generates a new random token, hashes it, and replaces the old token hash, expiry, and validity flag on the existing row. The update only affects the row if it belongs to the specified user, is currently invalid, and has already expired. If all conditions are met, the row becomes valid again with a fresh token and a new TTL.\n\n::warning\nIn-place rotation relaxes the stricter model where expired tokens always require a new row. Make sure higher-level logic still enforces `MAX_SESSION_LIFE`, anomaly checks, and MFA before calling this function.\n::\n\n::caution \nBe Very carful how you use `rotateInPlaceRefreshToken`, and `rotateOneUseRefreshToken` it completely bypass the reuse detection.\nBoth of these 2 not used in the life-cycles of the sessions.\n::\n\n### The Rotation Route\n\nStandalone service users call `POST \u002Fauth\u002Fuser\u002Frefresh-session` to rotate. The middleware chain is:\n\n```\nrequireRefreshToken, acceptCookieOnly, getFingerPrint, checkForActiveMfa rotateCredentials\n```\n\n`rotateCredentials` is the controller. It runs in this order:\n\n::steps{level=\"4\"}\n#### Rate limiting\nGuards against brute force with three limiters layered: IP limiter, token hash limiter, and a composite `ip_tokenhash` limiter. Each uses `guard()` with consecutive caches that escalate block duration on repeated violation.\n\n#### Anomaly detection\nCalls `strangeThings(rawRefreshToken, canary_id, ip, userAgent, true)` with `rotated = true`. The `true` flag tells the anomaly engine to also check `usage_count > 0` as a hard rejection. If anomalies are detected, the engine either triggers MFA (`202`) or demands re-login (`401`).\n\n#### Consume the old token\nCalls `consumeAndVerifyRefreshToken(rawRefreshToken)`. If the token is valid but `session_started_at` is older than `MAX_SESSION_LIFE`, the controller revokes it, clears both `session` and `iat` cookies, and returns `401 Session is expired`.\n\n#### Revoke the old token\nCalls `revokeRefreshToken(rawRefreshToken)` to set `valid = 0`.\n\n#### Issue new credentials\nGenerates a new refresh token with `generateRefreshToken(refresh_ttl, userId, sessionTTL)` and a new access token with `generateAccessToken`, the `sessionTTL` comes from the result of `consumeAndVerifyRefreshToken(rawRefreshToken)`. Sets the `session` cookie (with the new refresh token) and the `iat` cookie (with `Date.now()`). Blocks the old token hash in the rate limiter for 3 days.\n::\n\nSuccess response (`201`):\n\n```json\n{\n  \"message\": \"Refresh & access tokens rotated\",\n  \"accessToken\": \"\u003Csigned jwt>\",\n  \"accessIat\": \"1710000000000\"\n}\n```\n\n---\n\n## Logout\n\nStandalone service users call `POST \u002Fauth\u002Flogout`. The middleware chain is:\n\n```\nrequireRefreshToken, requireAccessToken, acceptCookieOnly, handleLogout\n```\n\nThe logout controller:\n\n1. Consumes the refresh token with `consumeAndVerifyRefreshToken` (atomic, prevents post-logout reuse)\n2. Revokes it with `revokeRefreshToken` (sets `valid = 0`)\n3. Blocks the token hash in the rate limiter for the remaining `refresh_ttl` duration\n4. Verifies the access token and blacklists its `jti` for 24 hours\n5. Clears the `session` and `iat` cookies\n\nSuccess response (`200`):\n\n```json\n{ \"ok\": true, \"message\": \"Logged out successfully\" }\n```\n\n---\n\n## Reuse Detection\n\nThe reuse detection system relies on two mechanisms working together: the `usage_count` column in `consumeAndVerifyRefreshToken`, and the anomaly engine's `rotated` flag check.\n\n### Through `consumeAndVerifyRefreshToken`\n\nWhen a token has `usage_count > 0` and someone tries to consume it again, the function assumes the token family is compromised. It revokes **all** valid refresh tokens for that user and returns `valid: false`. Both the attacker and the legitimate user are forced to re-authenticate.\n\n### Through the anomaly engine\n\nWhen `strangeThings` runs with `rotated = true` (the rotation route), it checks `usage_count > 0` as a hard rejection alongside its other checks. If the token was already consumed, the anomaly engine returns `valid: false, reqMFA: false`, which the rotation controller translates to a `401` re-login response.\n\n### Scenario walkthrough\n\n**An attacker obtains a valid, unconsumed refresh token.**\n\nThe attacker needs to replicate the user's exact fingerprint and `canary_id` cookie to avoid triggering MFA. If the fingerprint does not match, the anomaly engine flags it and sends an MFA challenge to the real user's email. If the attacker somehow passes the fingerprint check and consumes the token (`usage_count` goes to `1`), the legitimate user's next rotation attempt hits `usage_count > 0` and all sessions are revoked.\n\n**An attacker obtains a previously rotated (stale) token.**\n\nThe token already has `usage_count >= 1`. The attacker's attempt to consume it immediately triggers the revoke-all path. The legitimate user's current valid token is also invalidated. Both parties must re-authenticate, and the attacker cannot complete MFA without access to the user's email.\n\n---\n\n## Session Lifetime\n\nRefresh tokens have two independent lifetime controls:\n\n**Token TTL (`refresh_ttl`)**\nSet in milliseconds in the configuration. Controls the `expiresAt` column on each row. When a token expires, both `verifyRefreshToken` and `consumeAndVerifyRefreshToken` mark it `valid = 0` and set the user's `last_mfa_at` to `NULL`. This resets the `byPassAnomaliesFor` cooldown window, meaning the next time the user hits any anomaly condition (too many sessions, IP mismatch, canary mismatch, etc.) it will not be bypassed. A clean login from the same device with no anomalies still passes without MFA.\n\n**Session lifetime (`MAX_SESSION_LIFE`)**\nAlso in milliseconds. The rotation controller compares `Date.now() - session_started_at` against this value. If the session has been alive longer than `MAX_SESSION_LIFE`, the controller revokes the token and returns `401 Session is expired`, even if the token itself has not expired yet.\n\nThe distinction matters: `refresh_ttl` controls how long a single token lives. `MAX_SESSION_LIFE` controls how long the session chain (original token plus all its rotated successors) can stay alive, both controlled in the configuration.\n\n---\n\n## Session Limits\n\nThe anomaly engine enforces two session-count rules, both configured under `jwt.refresh_tokens`:\n\n**`maxAllowedSessionsPerUser`** limits the number of concurrent valid sessions. When a user exceeds this count, the anomaly engine flags the next rotation or access-check as requiring MFA. The user must prove their identity before the service issues new tokens.\n\n**`byPassAnomaliesFor`** is a cooldown period in milliseconds. If the user completed MFA within that window (`last_mfa_at + byPassAnomaliesFor > now`), the session-count check is skipped. This prevents MFA spam when a user legitimately logs in from multiple devices in quick succession.\n\nThe anomaly engine also flags rapid token creation: if more than 3 valid tokens were created in the last 10 minutes for the same user, the presented token is revoked and the session is terminated without MFA (hard `401`).\n\nMore details at [anomalies](\u002Fdocs\u002Fiam\u002Fanomalies)\n\n---\n\n## Configuration Reference\n\nAll refresh token options live under `jwt.refresh_tokens` in the object passed to `configuration()`.\n\n| Option | Type | Description |\n|---|---|---|\n| `refresh_ttl` | `number` | Token lifetime in milliseconds. Used as the TTL for newly generated tokens |\n| `domain` | `string` | Cookie domain for the `session` cookie (e.g. `example.com`) |\n| `MAX_SESSION_LIFE` | `number` | Maximum session chain lifetime in milliseconds. Rotation is rejected when the original `session_started_at` is older than this |\n| `maxAllowedSessionsPerUser` | `number` | Maximum concurrent valid refresh tokens per user before MFA is triggered |\n| `byPassAnomaliesFor` | `number` | MFA cooldown in milliseconds. Session-count anomalies are skipped if `last_mfa_at` is within this window |\n\n::note\n`refresh_ttl` and `MAX_SESSION_LIFE` serve different purposes. `refresh_ttl` is how long one token lives. `MAX_SESSION_LIFE` is how long the entire session chain can survive across rotations. Set `MAX_SESSION_LIFE` to something like 7 or 30 days depending on your security posture, and `refresh_ttl` to something shorter (e.g. 3 days). This forces periodic rotation while still capping absolute session duration.\n::\n",{"title":91,"description":2933},"5RMWOCgopJWtuJxyv8Xirt6PyN6JX-GmlsaxzkRR_RM",[2942,2943],{"title":87,"path":88,"stem":89,"children":-1},{"title":95,"path":96,"stem":97,"children":-1},{"id":851,"title":91,"body":2945,"description":2933,"extension":2934,"icon":2935,"meta":4540,"module":2937,"navigation":8,"path":92,"rawbody":2938,"seo":4541,"stem":93,"__hash__":2940},{"type":853,"value":2946,"toc":4513},[2947,2955,2963,2965,2967,2973,3093,3099,3101,3103,3189,3243,3253,3257,3259,3261,3267,3275,3277,3279,3281,3285,3289,3381,3467,3471,3485,3493,3497,3499,3571,3647,3655,3659,3687,3697,3699,3701,3703,3751,3755,3757,3805,3809,3811,3813,3815,3819,3821,3923,3937,3941,3943,4019,4021,4027,4035,4037,4041,4046,4050,4118,4122,4186,4188,4190,4194,4199,4201,4227,4231,4267,4269,4271,4279,4283,4291,4293,4305,4307,4311,4321,4325,4329,4331,4333,4335,4355,4367,4373,4375,4377,4381,4387,4395,4399,4403,4405,4407,4413,4495,4511],[856,2948,858,2949,863,2951,867,2953,871],{},[860,2950,862],{},[860,2952,866],{},[860,2954,870],{},[856,2956,874,2957,878,2959,882,2961,886],{},[860,2958,877],{},[860,2960,881],{},[860,2962,885],{},[888,2964],{},[891,2966,894],{"id":893},[856,2968,897,2969,900,2971,904],{},[860,2970,862],{},[860,2972,903],{},[906,2974,2975,2985],{},[909,2976,2977],{},[912,2978,2979,2981,2983],{},[915,2980,917],{},[915,2982,920],{},[915,2984,923],{},[925,2986,2987,2999,3013,3025,3037,3049,3063,3079],{},[912,2988,2989,2993,2997],{},[930,2990,2991],{},[860,2992,934],{},[930,2994,2995],{},[860,2996,939],{},[930,2998,942],{},[912,3000,3001,3005,3009],{},[930,3002,3003],{},[860,3004,949],{},[930,3006,3007],{},[860,3008,954],{},[930,3010,957,3011,961],{},[860,3012,960],{},[912,3014,3015,3019,3023],{},[930,3016,3017],{},[860,3018,968],{},[930,3020,3021],{},[860,3022,973],{},[930,3024,976],{},[912,3026,3027,3031,3035],{},[930,3028,3029],{},[860,3030,983],{},[930,3032,3033],{},[860,3034,988],{},[930,3036,991],{},[912,3038,3039,3043,3047],{},[930,3040,3041],{},[860,3042,998],{},[930,3044,3045],{},[860,3046,1003],{},[930,3048,1006],{},[912,3050,3051,3055,3059],{},[930,3052,3053],{},[860,3054,1013],{},[930,3056,3057],{},[860,3058,1003],{},[930,3060,1020,3061,1024],{},[860,3062,1023],{},[912,3064,3065,3069,3073],{},[930,3066,3067],{},[860,3068,877],{},[930,3070,3071],{},[860,3072,1035],{},[930,3074,3075,1040,3077,1043],{},[860,3076,881],{},[860,3078,885],{},[912,3080,3081,3085,3089],{},[930,3082,3083],{},[860,3084,1050],{},[930,3086,3087],{},[860,3088,1003],{},[930,3090,1057,3091,1061],{},[860,3092,1060],{},[856,3094,1064,3095,1067,3097,1070],{},[860,3096,968],{},[860,3098,949],{},[888,3100],{},[891,3102,1076],{"id":1075},[1078,3104,3105],{"className":1080,"code":1081,"language":1082,"meta":1083,"style":1083},[860,3106,3107,3125,3129,3151,3169,3179,3185],{"__ignoreMap":1083},[1087,3108,3109,3111,3113,3115,3117,3119,3121,3123],{"class":1089,"line":1090},[1087,3110,1094],{"class":1093},[1087,3112,1098],{"class":1097},[1087,3114,903],{"class":1101},[1087,3116,1104],{"class":1097},[1087,3118,1107],{"class":1093},[1087,3120,1111],{"class":1110},[1087,3122,1115],{"class":1114},[1087,3124,1118],{"class":1110},[1087,3126,3127],{"class":1089,"line":1121},[1087,3128,1124],{"emptyLinePlaceholder":8},[1087,3130,3131,3133,3135,3137,3139,3141,3143,3145,3147,3149],{"class":1089,"line":1127},[1087,3132,1131],{"class":1130},[1087,3134,1098],{"class":1097},[1087,3136,1137],{"class":1136},[1087,3138,1140],{"class":1097},[1087,3140,1013],{"class":1136},[1087,3142,1104],{"class":1097},[1087,3144,1148],{"class":1147},[1087,3146,1151],{"class":1093},[1087,3148,1155],{"class":1154},[1087,3150,1158],{"class":1097},[1087,3152,3153,3155,3157,3159,3161,3163,3165,3167],{"class":1089,"line":1161},[1087,3154,1164],{"class":1101},[1087,3156,1167],{"class":1097},[1087,3158,1170],{"class":1101},[1087,3160,1167],{"class":1097},[1087,3162,862],{"class":1101},[1087,3164,1167],{"class":1097},[1087,3166,1023],{"class":1101},[1087,3168,1181],{"class":1097},[1087,3170,3171,3173,3175,3177],{"class":1089,"line":1184},[1087,3172,1187],{"class":1101},[1087,3174,1167],{"class":1097},[1087,3176,934],{"class":1101},[1087,3178,1181],{"class":1097},[1087,3180,3181,3183],{"class":1089,"line":1196},[1087,3182,1199],{"class":1101},[1087,3184,1203],{"class":1202},[1087,3186,3187],{"class":1089,"line":1206},[1087,3188,1209],{"class":1097},[906,3190,3191,3201],{},[909,3192,3193],{},[912,3194,3195,3197,3199],{},[915,3196,1218],{},[915,3198,920],{},[915,3200,923],{},[925,3202,3203,3215,3227],{},[912,3204,3205,3209,3213],{},[930,3206,3207],{},[860,3208,1231],{},[930,3210,3211],{},[860,3212,1236],{},[930,3214,1239],{},[912,3216,3217,3221,3225],{},[930,3218,3219],{},[860,3220,1246],{},[930,3222,3223],{},[860,3224,1236],{},[930,3226,1253],{},[912,3228,3229,3233,3239],{},[930,3230,3231],{},[860,3232,1260],{},[930,3234,3235,1266,3237],{},[860,3236,1265],{},[860,3238,1269],{},[930,3240,1272,3241],{},[860,3242,1275],{},[856,3244,1278,3245,1282,3247,1286,3249,1289,3251,1292],{},[860,3246,1281],{},[860,3248,1285],{},[860,3250,1013],{},[860,3252,870],{},[856,3254,1295,3255,1298],{},[860,3256,903],{},[888,3258],{},[891,3260,1304],{"id":1303},[856,3262,1307,3263,1310,3265,1314],{},[860,3264,1281],{},[860,3266,1313],{},[856,3268,1317,3269,1140,3271,1323,3273,1327],{},[860,3270,1281],{},[860,3272,1322],{},[860,3274,1326],{},[888,3276],{},[891,3278,1333],{"id":1332},[856,3280,1336],{},[1338,3282,3283],{"id":1340},[860,3284,1343],{},[856,3286,1346,3287,1349],{},[860,3288,877],{},[1078,3290,3291],{"className":1080,"code":1352,"language":1082,"meta":1083,"style":1083},[860,3292,3293,3311,3315,3333,3337,3351,3377],{"__ignoreMap":1083},[1087,3294,3295,3297,3299,3301,3303,3305,3307,3309],{"class":1089,"line":1090},[1087,3296,1094],{"class":1093},[1087,3298,1098],{"class":1097},[1087,3300,1343],{"class":1101},[1087,3302,1104],{"class":1097},[1087,3304,1107],{"class":1093},[1087,3306,1111],{"class":1110},[1087,3308,1115],{"class":1114},[1087,3310,1118],{"class":1110},[1087,3312,3313],{"class":1089,"line":1121},[1087,3314,1124],{"emptyLinePlaceholder":8},[1087,3316,3317,3319,3321,3323,3325,3327,3329,3331],{"class":1089,"line":1127},[1087,3318,1131],{"class":1130},[1087,3320,1383],{"class":1136},[1087,3322,1386],{"class":1147},[1087,3324,1151],{"class":1093},[1087,3326,1391],{"class":1154},[1087,3328,1394],{"class":1097},[1087,3330,1397],{"class":1101},[1087,3332,1209],{"class":1097},[1087,3334,3335],{"class":1089,"line":1161},[1087,3336,1124],{"emptyLinePlaceholder":8},[1087,3338,3339,3341,3343,3345,3347,3349],{"class":1089,"line":1184},[1087,3340,1408],{"class":1093},[1087,3342,1411],{"class":1097},[1087,3344,1414],{"class":1101},[1087,3346,1167],{"class":1097},[1087,3348,983],{"class":1101},[1087,3350,1421],{"class":1097},[1087,3352,3353,3355,3357,3359,3361,3363,3365,3367,3369,3371,3373,3375],{"class":1089,"line":1196},[1087,3354,1426],{"class":1130},[1087,3356,1098],{"class":1097},[1087,3358,1246],{"class":1136},[1087,3360,1140],{"class":1097},[1087,3362,1435],{"class":1136},[1087,3364,1140],{"class":1097},[1087,3366,1440],{"class":1136},[1087,3368,1140],{"class":1097},[1087,3370,1013],{"class":1136},[1087,3372,1104],{"class":1097},[1087,3374,1148],{"class":1147},[1087,3376,1451],{"class":1101},[1087,3378,3379],{"class":1089,"line":1206},[1087,3380,1456],{"class":1097},[906,3382,3383,3393],{},[909,3384,3385],{},[912,3386,3387,3389,3391],{},[915,3388,1465],{},[915,3390,920],{},[915,3392,1470],{},[925,3394,3395,3407,3419,3431,3443,3455],{},[912,3396,3397,3401,3405],{},[930,3398,3399],{},[860,3400,983],{},[930,3402,3403],{},[860,3404,1483],{},[930,3406,1486],{},[912,3408,3409,3413,3417],{},[930,3410,3411],{},[860,3412,1246],{},[930,3414,3415],{},[860,3416,1236],{},[930,3418,1499],{},[912,3420,3421,3425,3429],{},[930,3422,3423],{},[860,3424,1435],{},[930,3426,3427],{},[860,3428,1510],{},[930,3430,1513],{},[912,3432,3433,3437,3441],{},[930,3434,3435],{},[860,3436,1440],{},[930,3438,3439],{},[860,3440,1265],{},[930,3442,1513],{},[912,3444,3445,3449,3453],{},[930,3446,3447],{},[860,3448,1013],{},[930,3450,3451],{},[860,3452,1265],{},[930,3454,1513],{},[912,3456,3457,3461,3465],{},[930,3458,3459],{},[860,3460,1544],{},[930,3462,3463],{},[860,3464,1510],{},[930,3466,1551],{},[856,3468,3469,1556],{},[860,3470,1343],{},[1558,3472,3473,3477],{},[1561,3474,1563,3475,1567],{},[860,3476,1566],{},[1561,3478,1570,3479,1573,3481,1577,3483,1581],{},[860,3480,1566],{},[860,3482,1576],{},[860,3484,1580],{},[1583,3486,3487],{},[856,3488,3489,1589,3491,1593],{},[860,3490,1343],{},[860,3492,1592],{},[1338,3494,3495],{"id":1596},[860,3496,1592],{},[856,3498,1601],{},[1078,3500,3501],{"className":1080,"code":1604,"language":1082,"meta":1083,"style":1083},[860,3502,3503,3521,3525,3543,3547,3563,3567],{"__ignoreMap":1083},[1087,3504,3505,3507,3509,3511,3513,3515,3517,3519],{"class":1089,"line":1090},[1087,3506,1094],{"class":1093},[1087,3508,1098],{"class":1097},[1087,3510,1592],{"class":1101},[1087,3512,1104],{"class":1097},[1087,3514,1107],{"class":1093},[1087,3516,1111],{"class":1110},[1087,3518,1115],{"class":1114},[1087,3520,1118],{"class":1110},[1087,3522,3523],{"class":1089,"line":1121},[1087,3524,1124],{"emptyLinePlaceholder":8},[1087,3526,3527,3529,3531,3533,3535,3537,3539,3541],{"class":1089,"line":1127},[1087,3528,1131],{"class":1130},[1087,3530,1383],{"class":1136},[1087,3532,1386],{"class":1147},[1087,3534,1151],{"class":1093},[1087,3536,1641],{"class":1154},[1087,3538,1394],{"class":1097},[1087,3540,1397],{"class":1101},[1087,3542,1209],{"class":1097},[1087,3544,3545],{"class":1089,"line":1161},[1087,3546,1124],{"emptyLinePlaceholder":8},[1087,3548,3549,3551,3553,3555,3557,3559,3561],{"class":1089,"line":1184},[1087,3550,1408],{"class":1093},[1087,3552,1411],{"class":1097},[1087,3554,1660],{"class":1147},[1087,3556,1414],{"class":1101},[1087,3558,1167],{"class":1097},[1087,3560,983],{"class":1101},[1087,3562,1421],{"class":1097},[1087,3564,3565],{"class":1089,"line":1196},[1087,3566,1673],{"class":1202},[1087,3568,3569],{"class":1089,"line":1206},[1087,3570,1456],{"class":1097},[906,3572,3573,3583],{},[909,3574,3575],{},[912,3576,3577,3579,3581],{},[915,3578,1465],{},[915,3580,920],{},[915,3582,1470],{},[925,3584,3585,3597,3609,3621,3635],{},[912,3586,3587,3591,3595],{},[930,3588,3589],{},[860,3590,983],{},[930,3592,3593],{},[860,3594,1483],{},[930,3596,1486],{},[912,3598,3599,3603,3607],{},[930,3600,3601],{},[860,3602,1246],{},[930,3604,3605],{},[860,3606,1236],{},[930,3608,1499],{},[912,3610,3611,3615,3619],{},[930,3612,3613],{},[860,3614,1435],{},[930,3616,3617],{},[860,3618,1510],{},[930,3620,1513],{},[912,3622,3623,3627,3631],{},[930,3624,3625],{},[860,3626,1734],{},[930,3628,3629],{},[860,3630,1265],{},[930,3632,1741,3633,1744],{},[860,3634,1050],{},[912,3636,3637,3641,3645],{},[930,3638,3639],{},[860,3640,1544],{},[930,3642,3643],{},[860,3644,1510],{},[930,3646,1551],{},[856,3648,1759,3649,1763,3651,1766,3653,1770],{},[860,3650,1762],{},[860,3652,877],{},[860,3654,1769],{},[856,3656,1773,3657,1777],{},[860,3658,1776],{},[1779,3660,3661,3663,3667,3669,3675,3677],{"level":1781},[1783,3662,1786],{"id":1785},[856,3664,1789,3665,1167],{},[860,3666,1792],{},[1783,3668,1796],{"id":1795},[856,3670,1799,3671,1802,3673,1167],{},[860,3672,1566],{},[860,3674,1805],{},[1783,3676,1809],{"id":1808},[856,3678,1812,3679,1815,3681,1819,3683,1824,3685,1167],{},[860,3680,1285],{},[860,3682,1818],{},[1821,3684,1823],{},[860,3686,1827],{},[856,3688,1830,3689,1834,3691,1838,3693,1841,3695,1844],{},[860,3690,1833],{},[860,3692,1837],{},[860,3694,1435],{},[860,3696,1343],{},[888,3698],{},[891,3700,191],{"id":1849},[1338,3702,1853],{"id":1852},[1078,3704,3705],{"className":1080,"code":1856,"language":1082,"meta":1083,"style":1083},[860,3706,3707,3725,3729],{"__ignoreMap":1083},[1087,3708,3709,3711,3713,3715,3717,3719,3721,3723],{"class":1089,"line":1090},[1087,3710,1094],{"class":1093},[1087,3712,1098],{"class":1097},[1087,3714,1867],{"class":1101},[1087,3716,1104],{"class":1097},[1087,3718,1107],{"class":1093},[1087,3720,1111],{"class":1110},[1087,3722,1115],{"class":1114},[1087,3724,1118],{"class":1110},[1087,3726,3727],{"class":1089,"line":1121},[1087,3728,1124],{"emptyLinePlaceholder":8},[1087,3730,3731,3733,3735,3737,3739,3741,3743,3745,3747,3749],{"class":1089,"line":1127},[1087,3732,1131],{"class":1130},[1087,3734,1098],{"class":1097},[1087,3736,1890],{"class":1136},[1087,3738,1104],{"class":1097},[1087,3740,1148],{"class":1147},[1087,3742,1151],{"class":1093},[1087,3744,1899],{"class":1154},[1087,3746,1394],{"class":1097},[1087,3748,1397],{"class":1101},[1087,3750,1209],{"class":1097},[856,3752,1908,3753,1911],{},[860,3754,1566],{},[1338,3756,1915],{"id":1914},[1078,3758,3759],{"className":1080,"code":1918,"language":1082,"meta":1083,"style":1083},[860,3760,3761,3779,3783],{"__ignoreMap":1083},[1087,3762,3763,3765,3767,3769,3771,3773,3775,3777],{"class":1089,"line":1090},[1087,3764,1094],{"class":1093},[1087,3766,1098],{"class":1097},[1087,3768,1929],{"class":1101},[1087,3770,1104],{"class":1097},[1087,3772,1107],{"class":1093},[1087,3774,1111],{"class":1110},[1087,3776,1115],{"class":1114},[1087,3778,1118],{"class":1110},[1087,3780,3781],{"class":1089,"line":1121},[1087,3782,1124],{"emptyLinePlaceholder":8},[1087,3784,3785,3787,3789,3791,3793,3795,3797,3799,3801,3803],{"class":1089,"line":1127},[1087,3786,1131],{"class":1130},[1087,3788,1098],{"class":1097},[1087,3790,1890],{"class":1136},[1087,3792,1104],{"class":1097},[1087,3794,1148],{"class":1147},[1087,3796,1151],{"class":1093},[1087,3798,1960],{"class":1154},[1087,3800,1394],{"class":1097},[1087,3802,1246],{"class":1101},[1087,3804,1209],{"class":1097},[856,3806,1969,3807,1972],{},[860,3808,1592],{},[888,3810],{},[891,3812,195],{"id":1977},[856,3814,1980],{},[1338,3816,3817],{"id":1983},[860,3818,1986],{},[856,3820,1989],{},[1078,3822,3823],{"className":1080,"code":1992,"language":1082,"meta":1083,"style":1083},[860,3824,3825,3843,3847,3861,3879,3889,3893,3897,3901,3915,3919],{"__ignoreMap":1083},[1087,3826,3827,3829,3831,3833,3835,3837,3839,3841],{"class":1089,"line":1090},[1087,3828,1094],{"class":1093},[1087,3830,1098],{"class":1097},[1087,3832,1986],{"class":1101},[1087,3834,1104],{"class":1097},[1087,3836,1107],{"class":1093},[1087,3838,1111],{"class":1110},[1087,3840,1115],{"class":1114},[1087,3842,1118],{"class":1110},[1087,3844,3845],{"class":1089,"line":1121},[1087,3846,1124],{"emptyLinePlaceholder":8},[1087,3848,3849,3851,3853,3855,3857,3859],{"class":1089,"line":1127},[1087,3850,1131],{"class":1130},[1087,3852,1383],{"class":1136},[1087,3854,1386],{"class":1147},[1087,3856,1151],{"class":1093},[1087,3858,2029],{"class":1154},[1087,3860,1158],{"class":1097},[1087,3862,3863,3865,3867,3869,3871,3873,3875,3877],{"class":1089,"line":1161},[1087,3864,1164],{"class":1101},[1087,3866,1167],{"class":1097},[1087,3868,1170],{"class":1101},[1087,3870,1167],{"class":1097},[1087,3872,862],{"class":1101},[1087,3874,1167],{"class":1097},[1087,3876,1023],{"class":1101},[1087,3878,1181],{"class":1097},[1087,3880,3881,3883,3885,3887],{"class":1089,"line":1184},[1087,3882,1187],{"class":1101},[1087,3884,1167],{"class":1097},[1087,3886,934],{"class":1101},[1087,3888,1181],{"class":1097},[1087,3890,3891],{"class":1089,"line":1196},[1087,3892,2064],{"class":1101},[1087,3894,3895],{"class":1089,"line":1206},[1087,3896,1209],{"class":1097},[1087,3898,3899],{"class":1089,"line":2071},[1087,3900,1124],{"emptyLinePlaceholder":8},[1087,3902,3903,3905,3907,3909,3911,3913],{"class":1089,"line":2076},[1087,3904,1408],{"class":1093},[1087,3906,1411],{"class":1097},[1087,3908,1414],{"class":1101},[1087,3910,1167],{"class":1097},[1087,3912,2087],{"class":1101},[1087,3914,1421],{"class":1097},[1087,3916,3917],{"class":1089,"line":2092},[1087,3918,2095],{"class":1202},[1087,3920,3921],{"class":1089,"line":2098},[1087,3922,1456],{"class":1097},[856,3924,2103,3925,2106,3927,2109,3929,2112,3931,2115,3933,2118,3935,2121],{},[860,3926,1343],{},[860,3928,1592],{},[860,3930,1867],{},[860,3932,903],{},[860,3934,1246],{},[860,3936,949],{},[1338,3938,3939],{"id":2124},[860,3940,2127],{},[856,3942,2130],{},[1078,3944,3945],{"className":1080,"code":2133,"language":1082,"meta":1083,"style":1083},[860,3946,3947,3965,3969,3983,4001,4011,4015],{"__ignoreMap":1083},[1087,3948,3949,3951,3953,3955,3957,3959,3961,3963],{"class":1089,"line":1090},[1087,3950,1094],{"class":1093},[1087,3952,1098],{"class":1097},[1087,3954,2127],{"class":1101},[1087,3956,1104],{"class":1097},[1087,3958,1107],{"class":1093},[1087,3960,1111],{"class":1110},[1087,3962,1115],{"class":1114},[1087,3964,1118],{"class":1110},[1087,3966,3967],{"class":1089,"line":1121},[1087,3968,1124],{"emptyLinePlaceholder":8},[1087,3970,3971,3973,3975,3977,3979,3981],{"class":1089,"line":1127},[1087,3972,1131],{"class":1130},[1087,3974,1383],{"class":1136},[1087,3976,1386],{"class":1147},[1087,3978,1151],{"class":1093},[1087,3980,2170],{"class":1154},[1087,3982,1158],{"class":1097},[1087,3984,3985,3987,3989,3991,3993,3995,3997,3999],{"class":1089,"line":1161},[1087,3986,1164],{"class":1101},[1087,3988,1167],{"class":1097},[1087,3990,1170],{"class":1101},[1087,3992,1167],{"class":1097},[1087,3994,862],{"class":1101},[1087,3996,1167],{"class":1097},[1087,3998,1023],{"class":1101},[1087,4000,1181],{"class":1097},[1087,4002,4003,4005,4007,4009],{"class":1089,"line":1184},[1087,4004,1187],{"class":1101},[1087,4006,1167],{"class":1097},[1087,4008,934],{"class":1101},[1087,4010,1181],{"class":1097},[1087,4012,4013],{"class":1089,"line":1196},[1087,4014,2064],{"class":1101},[1087,4016,4017],{"class":1089,"line":1206},[1087,4018,1209],{"class":1097},[856,4020,2211],{},[1583,4022,4023],{},[856,4024,2216,4025,2219],{},[860,4026,1060],{},[2221,4028,4029],{},[856,4030,2225,4031,2228,4033,2231],{},[860,4032,2127],{},[860,4034,1986],{},[1338,4036,2235],{"id":2234},[856,4038,2238,4039,2242],{},[860,4040,2241],{},[1078,4042,4044],{"className":4043,"code":2247,"language":2248},[2246],[860,4045,2247],{"__ignoreMap":1083},[856,4047,4048,2256],{},[860,4049,2255],{},[1779,4051,4052,4054,4060,4062,4076,4078,4092,4094,4100,4102],{"level":1781},[1783,4053,2262],{"id":2261},[856,4055,2265,4056,2269,4058,2273],{},[860,4057,2268],{},[860,4059,2272],{},[1783,4061,2277],{"id":2276},[856,4063,2280,4064,2284,4066,2288,4068,2292,4070,2295,4072,2299,4074,2303],{},[860,4065,2283],{},[860,4067,2287],{},[860,4069,2291],{},[860,4071,1818],{},[860,4073,2298],{},[860,4075,2302],{},[1783,4077,2307],{"id":2306},[856,4079,2280,4080,2313,4082,2316,4084,2319,4086,1286,4088,2325,4090,1167],{},[860,4081,2312],{},[860,4083,1050],{},[860,4085,1060],{},[860,4087,870],{},[860,4089,2324],{},[860,4091,2328],{},[1783,4093,2332],{"id":2331},[856,4095,2280,4096,2338,4098,1167],{},[860,4097,2337],{},[860,4099,1566],{},[1783,4101,2344],{"id":2343},[856,4103,2347,4104,2351,4106,2355,4108,2358,4110,2361,4112,2364,4114,2367,4116,2371],{},[860,4105,2350],{},[860,4107,2354],{},[860,4109,1734],{},[860,4111,2312],{},[860,4113,870],{},[860,4115,2324],{},[860,4117,2370],{},[856,4119,2374,4120,2378],{},[860,4121,2377],{},[1078,4123,4124],{"className":2381,"code":2382,"language":5,"meta":1083,"style":1083},[860,4125,4126,4130,4148,4166,4182],{"__ignoreMap":1083},[1087,4127,4128],{"class":1089,"line":1090},[1087,4129,2389],{"class":1097},[1087,4131,4132,4134,4136,4138,4140,4142,4144,4146],{"class":1089,"line":1121},[1087,4133,2395],{"class":2394},[1087,4135,2399],{"class":2398},[1087,4137,2402],{"class":2394},[1087,4139,1777],{"class":1147},[1087,4141,2407],{"class":1110},[1087,4143,2410],{"class":1114},[1087,4145,2402],{"class":1110},[1087,4147,1181],{"class":1097},[1087,4149,4150,4152,4154,4156,4158,4160,4162,4164],{"class":1089,"line":1127},[1087,4151,2395],{"class":2394},[1087,4153,2421],{"class":2398},[1087,4155,2402],{"class":2394},[1087,4157,1777],{"class":1147},[1087,4159,2407],{"class":1110},[1087,4161,2430],{"class":1114},[1087,4163,2402],{"class":1110},[1087,4165,1181],{"class":1097},[1087,4167,4168,4170,4172,4174,4176,4178,4180],{"class":1089,"line":1161},[1087,4169,2395],{"class":2394},[1087,4171,2441],{"class":2398},[1087,4173,2402],{"class":2394},[1087,4175,1777],{"class":1147},[1087,4177,2407],{"class":1110},[1087,4179,2450],{"class":1114},[1087,4181,2453],{"class":1110},[1087,4183,4184],{"class":1089,"line":1184},[1087,4185,1456],{"class":1097},[888,4187],{},[891,4189,107],{"id":2462},[856,4191,2238,4192,2468],{},[860,4193,2467],{},[1078,4195,4197],{"className":4196,"code":2472,"language":2248},[2246],[860,4198,2472],{"__ignoreMap":1083},[856,4200,2477],{},[2479,4202,4203,4207,4213,4217,4221],{},[1561,4204,2483,4205,2486],{},[860,4206,1592],{},[1561,4208,2489,4209,2492,4211,2495],{},[860,4210,1867],{},[860,4212,1566],{},[1561,4214,2498,4215,2501],{},[860,4216,1023],{},[1561,4218,2504,4219,2508],{},[860,4220,2507],{},[1561,4222,2511,4223,1286,4225,2516],{},[860,4224,870],{},[860,4226,2324],{},[856,4228,2374,4229,2378],{},[860,4230,2521],{},[1078,4232,4233],{"className":2381,"code":2524,"language":5,"meta":1083,"style":1083},[860,4234,4235],{"__ignoreMap":1083},[1087,4236,4237,4239,4241,4243,4245,4247,4249,4251,4253,4255,4257,4259,4261,4263,4265],{"class":1089,"line":1090},[1087,4238,2531],{"class":1097},[1087,4240,2402],{"class":2394},[1087,4242,2536],{"class":2398},[1087,4244,2402],{"class":2394},[1087,4246,1777],{"class":1147},[1087,4248,2544],{"class":2543},[1087,4250,1140],{"class":1097},[1087,4252,2402],{"class":2394},[1087,4254,2399],{"class":2398},[1087,4256,2402],{"class":2394},[1087,4258,1777],{"class":1147},[1087,4260,2407],{"class":1110},[1087,4262,2559],{"class":1114},[1087,4264,2402],{"class":1110},[1087,4266,2564],{"class":1097},[888,4268],{},[891,4270,2570],{"id":2569},[856,4272,2573,4273,2576,4275,2579,4277,2582],{},[860,4274,877],{},[860,4276,1592],{},[860,4278,2087],{},[1338,4280,2586,4281],{"id":2585},[860,4282,1592],{},[856,4284,2591,4285,2594,4287,2597,4289,2601],{},[860,4286,1818],{},[1821,4288,1823],{},[860,4290,2600],{},[1338,4292,2605],{"id":2604},[856,4294,2608,4295,2612,4297,2615,4299,2618,4301,2622,4303,2625],{},[860,4296,2611],{},[860,4298,2287],{},[860,4300,1818],{},[860,4302,2621],{},[860,4304,2302],{},[1338,4306,2629],{"id":2628},[856,4308,4309],{},[1821,4310,2634],{},[856,4312,2637,4313,2641,4315,2644,4317,2647,4319,2650],{},[860,4314,2640],{},[860,4316,877],{},[860,4318,885],{},[860,4320,1818],{},[856,4322,4323],{},[1821,4324,2655],{},[856,4326,2658,4327,2662],{},[860,4328,2661],{},[888,4330],{},[891,4332,2668],{"id":2667},[856,4334,2671],{},[856,4336,4337,2679,4341,2682,4343,1286,4345,2687,4347,2690,4349,1577,4351,2695,4353,2699],{},[1821,4338,2676,4339,2495],{},[860,4340,1023],{},[860,4342,1013],{},[860,4344,1343],{},[860,4346,1592],{},[860,4348,1566],{},[860,4350,1576],{},[860,4352,1580],{},[860,4354,2698],{},[856,4356,4357,2707,4361,2711,4363,2714,4365,2717],{},[1821,4358,2704,4359,2495],{},[860,4360,1060],{},[860,4362,2710],{},[860,4364,1060],{},[860,4366,2328],{},[856,4368,2720,4369,2723,4371,2726],{},[860,4370,1023],{},[860,4372,1060],{},[888,4374],{},[891,4376,2732],{"id":2731},[856,4378,2735,4379,1777],{},[860,4380,2738],{},[856,4382,4383,2746],{},[1821,4384,4385],{},[860,4386,2745],{},[856,4388,4389,2753,4393,2757],{},[1821,4390,4391],{},[860,4392,2698],{},[860,4394,2756],{},[856,4396,2760,4397,2303],{},[860,4398,2302],{},[856,4400,2765,4401],{},[2767,4402,2770],{"href":2769},[888,4404],{},[891,4406,2776],{"id":2775},[856,4408,2779,4409,2782,4411,1167],{},[860,4410,2738],{},[860,4412,2785],{},[906,4414,4415,4425],{},[909,4416,4417],{},[912,4418,4419,4421,4423],{},[915,4420,2794],{},[915,4422,920],{},[915,4424,923],{},[925,4426,4427,4439,4455,4469,4481],{},[912,4428,4429,4433,4437],{},[930,4430,4431],{},[860,4432,1023],{},[930,4434,4435],{},[860,4436,1236],{},[930,4438,2813],{},[912,4440,4441,4445,4449],{},[930,4442,4443],{},[860,4444,2820],{},[930,4446,4447],{},[860,4448,1510],{},[930,4450,2827,4451,2830,4453,2495],{},[860,4452,870],{},[860,4454,2833],{},[912,4456,4457,4461,4465],{},[930,4458,4459],{},[860,4460,1060],{},[930,4462,4463],{},[860,4464,1236],{},[930,4466,2846,4467,2849],{},[860,4468,1050],{},[912,4470,4471,4475,4479],{},[930,4472,4473],{},[860,4474,2745],{},[930,4476,4477],{},[860,4478,1236],{},[930,4480,2862],{},[912,4482,4483,4487,4491],{},[930,4484,4485],{},[860,4486,2698],{},[930,4488,4489],{},[860,4490,1236],{},[930,4492,2875,4493,2878],{},[860,4494,1576],{},[2880,4496,4497],{},[856,4498,4499,1286,4501,2888,4503,2891,4505,2894,4507,2897,4509,2900],{},[860,4500,1023],{},[860,4502,1060],{},[860,4504,1023],{},[860,4506,1060],{},[860,4508,1060],{},[860,4510,1023],{},[2902,4512,2904],{},{"title":1083,"searchDepth":1121,"depth":1121,"links":4514},[4515,4516,4517,4518,4522,4526,4531,4532,4537,4538,4539],{"id":893,"depth":1121,"text":894},{"id":1075,"depth":1121,"text":1076},{"id":1303,"depth":1121,"text":1304},{"id":1332,"depth":1121,"text":1333,"children":4519},[4520,4521],{"id":1340,"depth":1127,"text":1343},{"id":1596,"depth":1127,"text":1592},{"id":1849,"depth":1121,"text":191,"children":4523},[4524,4525],{"id":1852,"depth":1127,"text":1853},{"id":1914,"depth":1127,"text":1915},{"id":1977,"depth":1121,"text":195,"children":4527},[4528,4529,4530],{"id":1983,"depth":1127,"text":1986},{"id":2124,"depth":1127,"text":2127},{"id":2234,"depth":1127,"text":2235},{"id":2462,"depth":1121,"text":107},{"id":2569,"depth":1121,"text":2570,"children":4533},[4534,4535,4536],{"id":2585,"depth":1127,"text":2927},{"id":2604,"depth":1127,"text":2605},{"id":2628,"depth":1127,"text":2629},{"id":2667,"depth":1121,"text":2668},{"id":2731,"depth":1121,"text":2732},{"id":2775,"depth":1121,"text":2776},{},{"title":91,"description":2933},1780436280423]