[{"data":1,"prerenderedAt":5608},["ShallowReactive",2],{"navigation_docs":3,"-docs-recipes-add-authentication":493,"-docs-recipes-add-authentication-surround":5603},[4,46,135,218,257,292,355,377,420],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":45},"Getting Started","i-lucide-rocket","\u002Fdocs\u002Fgetting-started","docs\u002F01.getting-started",[10,15,20,25,30,35,40],{"title":11,"path":12,"stem":13,"icon":14},"What is Heximon?","\u002Fdocs\u002Fgetting-started\u002Fintroduction","docs\u002F01.getting-started\u002F01.introduction","i-lucide-info",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","docs\u002F01.getting-started\u002F02.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Quick Start","\u002Fdocs\u002Fgetting-started\u002Fquick-start","docs\u002F01.getting-started\u002F03.quick-start","i-lucide-zap",{"title":26,"path":27,"stem":28,"icon":29},"Core Concepts","\u002Fdocs\u002Fgetting-started\u002Fcore-concepts","docs\u002F01.getting-started\u002F04.core-concepts","i-lucide-shapes",{"title":31,"path":32,"stem":33,"icon":34},"Examples","\u002Fdocs\u002Fgetting-started\u002Fexamples","docs\u002F01.getting-started\u002F05.examples","i-lucide-library",{"title":36,"path":37,"stem":38,"icon":39},"Where Next?","\u002Fdocs\u002Fgetting-started\u002Fwhere-next","docs\u002F01.getting-started\u002F06.where-next","i-lucide-signpost",{"title":41,"path":42,"stem":43,"icon":44},"Troubleshooting","\u002Fdocs\u002Fgetting-started\u002Ftroubleshooting","docs\u002F01.getting-started\u002F07.troubleshooting","i-lucide-life-buoy",false,{"title":47,"icon":48,"path":49,"stem":50,"children":51,"page":45},"Essentials","i-lucide-boxes","\u002Fdocs\u002Fessentials","docs\u002F02.essentials",[52,56,61,66,71,76,81,86,91,96,101,105,110,115,120,125,130],{"title":53,"path":54,"stem":55,"icon":48},"Modules & DI","\u002Fdocs\u002Fessentials\u002Fmodules-and-di","docs\u002F02.essentials\u002F01.modules-and-di",{"title":57,"path":58,"stem":59,"icon":60},"Controllers","\u002Fdocs\u002Fessentials\u002Fcontrollers","docs\u002F02.essentials\u002F02.controllers","i-lucide-route",{"title":62,"path":63,"stem":64,"icon":65},"Validation & DTOs","\u002Fdocs\u002Fessentials\u002Fvalidation","docs\u002F02.essentials\u002F03.validation","i-lucide-shield-check",{"title":67,"path":68,"stem":69,"icon":70},"Contracts","\u002Fdocs\u002Fessentials\u002Fcontracts","docs\u002F02.essentials\u002F04.contracts","i-lucide-file-signature",{"title":72,"path":73,"stem":74,"icon":75},"REST Client","\u002Fdocs\u002Fessentials\u002Fclient","docs\u002F02.essentials\u002F05.client","i-lucide-plug",{"title":77,"path":78,"stem":79,"icon":80},"Database","\u002Fdocs\u002Fessentials\u002Fdatabase","docs\u002F02.essentials\u002F06.database","i-lucide-database",{"title":82,"path":83,"stem":84,"icon":85},"Drizzle ORM","\u002Fdocs\u002Fessentials\u002Fdrizzle","docs\u002F02.essentials\u002F07.drizzle","i-lucide-database-zap",{"title":87,"path":88,"stem":89,"icon":90},"Persistence & Transactions","\u002Fdocs\u002Fessentials\u002Fpersistence","docs\u002F02.essentials\u002F08.persistence","i-lucide-database-backup",{"title":92,"path":93,"stem":94,"icon":95},"Migrations","\u002Fdocs\u002Fessentials\u002Fmigrations","docs\u002F02.essentials\u002F09.migrations","i-lucide-git-branch",{"title":97,"path":98,"stem":99,"icon":100},"Authentication","\u002Fdocs\u002Fessentials\u002Fauthentication","docs\u002F02.essentials\u002F10.authentication","i-lucide-key-round",{"title":102,"path":103,"stem":104,"icon":65},"Permissions & scopes","\u002Fdocs\u002Fessentials\u002Fpermissions","docs\u002F02.essentials\u002F11.permissions",{"title":106,"path":107,"stem":108,"icon":109},"Error Handling","\u002Fdocs\u002Fessentials\u002Ferrors","docs\u002F02.essentials\u002F12.errors","i-lucide-triangle-alert",{"title":111,"path":112,"stem":113,"icon":114},"Testing a Heximon App","\u002Fdocs\u002Fessentials\u002Ftesting","docs\u002F02.essentials\u002F13.testing","i-lucide-flask-conical",{"title":116,"path":117,"stem":118,"icon":119},"Test app harness","\u002Fdocs\u002Fessentials\u002Fcreatetestapp","docs\u002F02.essentials\u002F14.createtestapp","i-lucide-test-tube",{"title":121,"path":122,"stem":123,"icon":124},"Logging & Observability","\u002Fdocs\u002Fessentials\u002Flogging","docs\u002F02.essentials\u002F17.logging","i-lucide-scroll-text",{"title":126,"path":127,"stem":128,"icon":129},"Context & Lifecycle","\u002Fdocs\u002Fessentials\u002Fcontext-and-lifecycle","docs\u002F02.essentials\u002F18.context-and-lifecycle","i-lucide-radio",{"title":131,"path":132,"stem":133,"icon":134},"Project Structure","\u002Fdocs\u002Fessentials\u002Fproject-structure","docs\u002F02.essentials\u002F19.project-structure","i-lucide-folder-tree",{"title":136,"icon":137,"path":138,"stem":139,"children":140,"page":45},"Capabilities","i-lucide-sparkles","\u002Fdocs\u002Fcapabilities","docs\u002F03.capabilities",[141,145,150,155,160,164,169,174,179,184,188,193,198,203,208,213],{"title":142,"path":143,"stem":144,"icon":137},"Overview","\u002Fdocs\u002Fcapabilities\u002Foverview","docs\u002F03.capabilities\u002F01.overview",{"title":146,"path":147,"stem":148,"icon":149},"Events","\u002Fdocs\u002Fcapabilities\u002Fevents","docs\u002F03.capabilities\u002F02.events","i-lucide-radio-tower",{"title":151,"path":152,"stem":153,"icon":154},"Queue","\u002Fdocs\u002Fcapabilities\u002Fbackground-jobs","docs\u002F03.capabilities\u002F03.background-jobs","i-lucide-list-ordered",{"title":156,"path":157,"stem":158,"icon":159},"Scheduled Work","\u002Fdocs\u002Fcapabilities\u002Fscheduled-jobs","docs\u002F03.capabilities\u002F04.scheduled-jobs","i-lucide-clock",{"title":161,"path":162,"stem":163,"icon":85},"Caching","\u002Fdocs\u002Fcapabilities\u002Fcaching","docs\u002F03.capabilities\u002F05.caching",{"title":165,"path":166,"stem":167,"icon":168},"Key\u002FValue Storage","\u002Fdocs\u002Fcapabilities\u002Fkey-value","docs\u002F03.capabilities\u002F06.key-value","i-lucide-hard-drive",{"title":170,"path":171,"stem":172,"icon":173},"Blob Storage","\u002Fdocs\u002Fcapabilities\u002Fblob","docs\u002F03.capabilities\u002F07.blob","i-lucide-folder-open",{"title":175,"path":176,"stem":177,"icon":178},"Health & Readiness","\u002Fdocs\u002Fcapabilities\u002Fhealth","docs\u002F03.capabilities\u002F08.health","i-lucide-heart-pulse",{"title":180,"path":181,"stem":182,"icon":183},"Notifications","\u002Fdocs\u002Fcapabilities\u002Fnotifications","docs\u002F03.capabilities\u002F09.notifications","i-lucide-mail",{"title":185,"path":186,"stem":187,"icon":65},"Idempotency","\u002Fdocs\u002Fcapabilities\u002Fidempotency","docs\u002F03.capabilities\u002F10.idempotency",{"title":189,"path":190,"stem":191,"icon":192},"Security Hardening","\u002Fdocs\u002Fcapabilities\u002Fsecurity","docs\u002F03.capabilities\u002F11.security","i-lucide-shield",{"title":194,"path":195,"stem":196,"icon":197},"Observability & Tracing","\u002Fdocs\u002Fcapabilities\u002Fobservability","docs\u002F03.capabilities\u002F12.observability","i-lucide-activity",{"title":199,"path":200,"stem":201,"icon":202},"OpenAPI","\u002Fdocs\u002Fcapabilities\u002Fopenapi","docs\u002F03.capabilities\u002F13.openapi","i-lucide-book-marked",{"title":204,"path":205,"stem":206,"icon":207},"MCP Server","\u002Fdocs\u002Fcapabilities\u002Fmcp","docs\u002F03.capabilities\u002F14.mcp","i-lucide-bot",{"title":209,"path":210,"stem":211,"icon":212},"Schema DTOs","\u002Fdocs\u002Fcapabilities\u002Fschema","docs\u002F03.capabilities\u002F15.schema","i-lucide-file-json",{"title":214,"path":215,"stem":216,"icon":217},"Branded IDs","\u002Fdocs\u002Fcapabilities\u002Fbranded-ids","docs\u002F03.capabilities\u002F16.branded-ids","i-lucide-tag",{"title":219,"icon":220,"path":221,"stem":222,"children":223,"page":45},"Architecture","i-lucide-building","\u002Fdocs\u002Farchitecture","docs\u002F04.architecture",[224,227,232,237,242,247,252],{"title":142,"path":225,"stem":226,"icon":220},"\u002Fdocs\u002Farchitecture\u002Foverview","docs\u002F04.architecture\u002F01.overview",{"title":228,"path":229,"stem":230,"icon":231},"Domain-Driven Design","\u002Fdocs\u002Farchitecture\u002Fddd","docs\u002F04.architecture\u002F02.ddd","i-lucide-hexagon",{"title":233,"path":234,"stem":235,"icon":236},"CQRS","\u002Fdocs\u002Farchitecture\u002Fcqrs","docs\u002F04.architecture\u002F03.cqrs","i-lucide-split",{"title":238,"path":239,"stem":240,"icon":241},"Repository Pattern","\u002Fdocs\u002Farchitecture\u002Frepository-pattern","docs\u002F04.architecture\u002F04.repository-pattern","i-lucide-archive",{"title":243,"path":244,"stem":245,"icon":246},"Integration Events","\u002Fdocs\u002Farchitecture\u002Fintegration-events","docs\u002F04.architecture\u002F05.integration-events","i-lucide-send-horizontal",{"title":248,"path":249,"stem":250,"icon":251},"Saga Orchestration","\u002Fdocs\u002Farchitecture\u002Fsagas","docs\u002F04.architecture\u002F06.sagas","i-lucide-hourglass",{"title":253,"path":254,"stem":255,"icon":256},"Durable Workflows","\u002Fdocs\u002Farchitecture\u002Fworkflows","docs\u002F04.architecture\u002F07.workflows","i-lucide-workflow",{"title":258,"icon":259,"path":260,"stem":261,"children":262,"page":45},"Realtime","i-lucide-cable","\u002Fdocs\u002Frealtime","docs\u002F05.realtime",[263,266,270,274,279,284,288],{"title":142,"path":264,"stem":265,"icon":259},"\u002Fdocs\u002Frealtime\u002Foverview","docs\u002F05.realtime\u002F01.overview",{"title":267,"path":268,"stem":269,"icon":129},"WebSockets","\u002Fdocs\u002Frealtime\u002Fwebsockets","docs\u002F05.realtime\u002F02.websockets",{"title":271,"path":272,"stem":273,"icon":70},"WebSocket Contracts","\u002Fdocs\u002Frealtime\u002Fwebsocket-contracts","docs\u002F05.realtime\u002F03.websocket-contracts",{"title":275,"path":276,"stem":277,"icon":278},"Server-Sent Events","\u002Fdocs\u002Frealtime\u002Fserver-sent-events","docs\u002F05.realtime\u002F04.server-sent-events","i-lucide-rss",{"title":280,"path":281,"stem":282,"icon":283},"Typed Clients","\u002Fdocs\u002Frealtime\u002Ftyped-clients","docs\u002F05.realtime\u002F05.typed-clients","i-lucide-plug-zap",{"title":285,"path":286,"stem":287,"icon":259},"AsyncAPI","\u002Fdocs\u002Frealtime\u002Fasyncapi","docs\u002F05.realtime\u002F06.asyncapi",{"title":289,"path":290,"stem":291,"icon":80},"Durable Objects","\u002Fdocs\u002Frealtime\u002Fdurable-objects","docs\u002F05.realtime\u002F07.durable-objects",{"title":293,"icon":294,"path":295,"stem":296,"children":297,"page":45},"Deploy","i-lucide-cloud-upload","\u002Fdocs\u002Fdeploy","docs\u002F06.deploy",[298,301,306,311,316,321,326,331,336,341,345,350],{"title":142,"path":299,"stem":300,"icon":6},"\u002Fdocs\u002Fdeploy\u002Foverview","docs\u002F06.deploy\u002F01.overview",{"title":302,"path":303,"stem":304,"icon":305},"Node","\u002Fdocs\u002Fdeploy\u002Fnode","docs\u002F06.deploy\u002F02.node","la:node-js",{"title":307,"path":308,"stem":309,"icon":310},"Cloudflare","\u002Fdocs\u002Fdeploy\u002Fcloudflare","docs\u002F06.deploy\u002F03.cloudflare","simple-icons:cloudflare",{"title":312,"path":313,"stem":314,"icon":315},"Vercel","\u002Fdocs\u002Fdeploy\u002Fvercel","docs\u002F06.deploy\u002F04.vercel","ri:vercel-fill",{"title":317,"path":318,"stem":319,"icon":320},"Netlify","\u002Fdocs\u002Fdeploy\u002Fnetlify","docs\u002F06.deploy\u002F05.netlify","i-simple-icons-netlify",{"title":322,"path":323,"stem":324,"icon":325},"AWS Lambda","\u002Fdocs\u002Fdeploy\u002Faws-lambda","docs\u002F06.deploy\u002F06.aws-lambda","hugeicons:aws-lambda",{"title":327,"path":328,"stem":329,"icon":330},"Deno Deploy","\u002Fdocs\u002Fdeploy\u002Fdeno-deploy","docs\u002F06.deploy\u002F07.deno-deploy","simple-icons:denodeploy",{"title":332,"path":333,"stem":334,"icon":335},"Nitro","\u002Fdocs\u002Fdeploy\u002Fnitro","docs\u002F06.deploy\u002F08.nitro","i-unjs-nitro",{"title":337,"path":338,"stem":339,"icon":340},"Nuxt","\u002Fdocs\u002Fdeploy\u002Fnuxt","docs\u002F06.deploy\u002F09.nuxt","i-devicon-nuxt",{"title":342,"path":343,"stem":344,"icon":236},"Mixed-runtime split","\u002Fdocs\u002Fdeploy\u002Fmixed-runtime-split","docs\u002F06.deploy\u002F10.mixed-runtime-split",{"title":346,"path":347,"stem":348,"icon":349},"Hosts","\u002Fdocs\u002Fdeploy\u002Fhosts","docs\u002F06.deploy\u002F11.hosts","i-lucide-map",{"title":351,"path":352,"stem":353,"icon":354},"Cross-service events","\u002Fdocs\u002Fdeploy\u002Fcross-service-events","docs\u002F06.deploy\u002F12.cross-service-events","i-lucide-network",{"title":356,"icon":357,"path":358,"stem":359,"children":360,"page":45},"CLI","i-lucide-terminal","\u002Fdocs\u002Fcli","docs\u002F07.cli",[361,365,369,373],{"title":362,"path":363,"stem":364,"icon":357},"The heximon CLI","\u002Fdocs\u002Fcli\u002Foverview","docs\u002F07.cli\u002F01.overview",{"title":366,"path":367,"stem":368,"icon":137},"Create a New App","\u002Fdocs\u002Fcli\u002Fcreate","docs\u002F07.cli\u002F02.create",{"title":370,"path":371,"stem":372,"icon":95},"Migrations from the CLI","\u002Fdocs\u002Fcli\u002Fmigrations","docs\u002F07.cli\u002F03.migrations",{"title":374,"path":375,"stem":376,"icon":6},"Deploying a Heximon app","\u002Fdocs\u002Fcli\u002Fdeploy","docs\u002F07.cli\u002F04.deploy",{"title":378,"icon":379,"path":380,"stem":381,"children":382,"page":45},"Under the Hood","i-lucide-wrench","\u002Fdocs\u002Funder-the-hood","docs\u002F08.under-the-hood",[383,388,393,397,402,407,411,415],{"title":384,"path":385,"stem":386,"icon":387},"How the Compiler Works","\u002Fdocs\u002Funder-the-hood\u002Fhow-the-compiler-works","docs\u002F08.under-the-hood\u002F01.how-the-compiler-works","i-lucide-cog",{"title":389,"path":390,"stem":391,"icon":392},"Compiler","\u002Fdocs\u002Funder-the-hood\u002Fextending-the-compiler","docs\u002F08.under-the-hood\u002F02.extending-the-compiler","i-lucide-cpu",{"title":394,"path":395,"stem":396,"icon":357},"Compiler Diagnostics","\u002Fdocs\u002Funder-the-hood\u002Fcompiler-diagnostics","docs\u002F08.under-the-hood\u002F03.compiler-diagnostics",{"title":398,"path":399,"stem":400,"icon":401},"Library Mode","\u002Fdocs\u002Funder-the-hood\u002Flibrary-mode","docs\u002F08.under-the-hood\u002F04.library-mode","i-lucide-package",{"title":403,"path":404,"stem":405,"icon":406},"Other bundlers","\u002Fdocs\u002Funder-the-hood\u002Falternate-bundlers","docs\u002F08.under-the-hood\u002F05.alternate-bundlers","i-lucide-blocks",{"title":408,"path":409,"stem":410,"icon":401},"Packages","\u002Fdocs\u002Funder-the-hood\u002Fpackages","docs\u002F08.under-the-hood\u002F06.packages",{"title":412,"path":413,"stem":414,"icon":207},"AI Skills","\u002Fdocs\u002Funder-the-hood\u002Fai-and-agents","docs\u002F08.under-the-hood\u002F07.ai-and-agents",{"title":416,"path":417,"stem":418,"icon":419},"Advanced DI","\u002Fdocs\u002Funder-the-hood\u002Fadvanced-di","docs\u002F08.under-the-hood\u002F08.advanced-di","i-lucide-git-fork",{"title":421,"icon":422,"path":423,"stem":424,"children":425,"page":45},"Recipes","i-lucide-book-open-check","\u002Fdocs\u002Frecipes","docs\u002F09.recipes",[426,431,436,440,444,448,452,456,460,465,469,474,479,484,488],{"title":427,"path":428,"stem":429,"icon":430},"Send an Email","\u002Fdocs\u002Frecipes\u002Fsend-an-email","docs\u002F09.recipes\u002F01.send-an-email","i-lucide-send",{"title":432,"path":433,"stem":434,"icon":435},"Upload a File","\u002Fdocs\u002Frecipes\u002Fupload-a-file","docs\u002F09.recipes\u002F02.upload-a-file","i-lucide-upload",{"title":437,"path":438,"stem":439,"icon":24},"Cache an Expensive Call","\u002Fdocs\u002Frecipes\u002Fcache-an-expensive-call","docs\u002F09.recipes\u002F03.cache-an-expensive-call",{"title":441,"path":442,"stem":443,"icon":154},"Paginate a List","\u002Fdocs\u002Frecipes\u002Fpaginate-a-list","docs\u002F09.recipes\u002F04.paginate-a-list",{"title":445,"path":446,"stem":447,"icon":65},"Protect Admin Routes","\u002Fdocs\u002Frecipes\u002Fprotect-admin-routes","docs\u002F09.recipes\u002F05.protect-admin-routes",{"title":449,"path":450,"stem":451,"icon":178},"Health Probes and a Nightly Job","\u002Fdocs\u002Frecipes\u002Fhealth-probes-and-a-nightly-job","docs\u002F09.recipes\u002F06.health-probes-and-a-nightly-job",{"title":453,"path":454,"stem":455,"icon":80},"Build a CRUD API","\u002Fdocs\u002Frecipes\u002Fcrud-api","docs\u002F09.recipes\u002F07.crud-api",{"title":457,"path":458,"stem":459,"icon":100},"Add Authentication","\u002Fdocs\u002Frecipes\u002Fadd-authentication","docs\u002F09.recipes\u002F08.add-authentication",{"title":461,"path":462,"stem":463,"icon":464},"Background Jobs","\u002Fdocs\u002Frecipes\u002Fbackground-jobs","docs\u002F09.recipes\u002F09.background-jobs","i-lucide-timer",{"title":466,"path":467,"stem":468,"icon":149},"Decouple Features with Events","\u002Fdocs\u002Frecipes\u002Fevent-driven-module","docs\u002F09.recipes\u002F10.event-driven-module",{"title":470,"path":471,"stem":472,"icon":473},"Build a DDD Aggregate","\u002Fdocs\u002Frecipes\u002Fddd-aggregate","docs\u002F09.recipes\u002F11.ddd-aggregate","i-lucide-box",{"title":475,"path":476,"stem":477,"icon":478},"Publish a Feature Package","\u002Fdocs\u002Frecipes\u002Ffeature-package","docs\u002F09.recipes\u002F12.feature-package","i-lucide-package-open",{"title":480,"path":481,"stem":482,"icon":483},"Reliable Integration Events","\u002Fdocs\u002Frecipes\u002Freliable-integration-events","docs\u002F09.recipes\u002F13.reliable-integration-events","i-lucide-inbox",{"title":485,"path":486,"stem":487,"icon":95},"Reactive Choreography","\u002Fdocs\u002Frecipes\u002Freactive-choreography","docs\u002F09.recipes\u002F14.reactive-choreography",{"title":489,"path":490,"stem":491,"icon":492},"Build a Production CRUD API","\u002Fdocs\u002Frecipes\u002Fproduction-crud-api","docs\u002F09.recipes\u002F15.production-crud-api","i-lucide-layers",{"id":494,"title":457,"body":495,"description":5596,"extension":5597,"links":5598,"meta":5599,"navigation":5600,"path":458,"seo":5601,"stem":459,"__hash__":5602},"docs\u002Fdocs\u002F09.recipes\u002F08.add-authentication.md",{"type":496,"value":497,"toc":5586},"minimark",[498,502,561,577,582,592,1233,1266,1270,1276,1765,1769,1783,1801,3836,3865,3869,3924,4400,4418,4422,4435,4835,4961,4978,5003,5251,5255,5262,5449,5453,5472,5499,5520,5524,5582],[499,500,501],"p",{},"Protect your routes end-to-end. By the end you'll have:",[503,504,505,522,531,540],"ul",{},[506,507,508,512,513,516,517,521],"li",{},[509,510,511],"code",{},"POST \u002Fauth\u002Fregister"," and ",[509,514,515],{},"POST \u002Fauth\u002Flogin"," issuing ",[518,519,520],"strong",{},"signed JWTs",", with constant-time password\nhashing so a wrong password and a missing account can't be told apart by timing.",[506,523,524,525,530],{},"A request-scoped ",[518,526,527],{},[509,528,529],{},"AuthContext"," any handler reads the caller's id and scopes from.",[506,532,533,534,539],{},"The shipped ",[518,535,536],{},[509,537,538],{},"JWTAuthMiddleware",", listed directly, verifying the bearer token (or cookie) before every\nhandler.",[506,541,542,543,548,549,552,553,556,557,560],{},"A scope-guarded ",[518,544,545],{},[509,546,547],{},"GET \u002Fadmin\u002Fstats"," returning ",[509,550,551],{},"401"," to anonymous callers, ",[509,554,555],{},"403"," to under-scoped ones,\nand ",[509,558,559],{},"200"," to an admin.",[499,562,563,564,567,568,571,572,576],{},"Everything is wired by class identity and constructor signatures — no decorators. The one twist: the auth\nprimitives live in ",[509,565,566],{},"@heximon\u002Fauth",", so the compiler can't read their constructors from your source; you bind\nthem through ",[509,569,570],{},"useFactory"," providers whose parameter types declare their deps — except where a framework\nclass's own constructor is ",[573,574,575],"em",{},"already"," plain DI tokens, which needs no factory at all.",[578,579,581],"h2",{"id":580},"_1-generate-the-signing-key-pair","1. Generate the signing key pair",[499,583,584,585,588,589,591],{},"A JWT is signed with a private key and verified with the matching public key. Hold both halves of one\ngenerated pair as a single DI token so the signer and verifier never drift apart. An ",[518,586,587],{},"async"," ",[509,590,570],{},"\n(step 4) resolves the pair once at boot — no key literal in source.",[593,594,600],"pre",{"className":595,"code":596,"filename":597,"language":598,"meta":599,"style":599},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import { Base64JWK, type GeneratedKeyPair, JwkService, JWTVerifier } from \"@heximon\u002Fauth\";\nimport { Platform } from \"@heximon\u002Fruntime\";\n\nexport class IdentityKeyPair {\n  \u002F\u002F An EC keygen (ES256, P-256) is ~2ms versus an RSA-2048 keygen's tens-to-hundreds of ms, so the\n  \u002F\u002F boot-time `useFactory` that resolves it stays off the critical path.\n  private static readonly algorithm: string = \"ES256\";\n  private static readonly environmentVariable: string = \"HEXIMON_JWK\";\n\n  public readonly privateKey: GeneratedKeyPair[\"privateKey\"];\n  public readonly publicKey: GeneratedKeyPair[\"publicKey\"];\n\n  public constructor(keyPair: GeneratedKeyPair) {\n    this.privateKey = keyPair.privateKey;\n    this.publicKey = keyPair.publicKey;\n  }\n\n  \u002F\u002F HEXIMON_JWK when set (production, survives a restart\u002Fscale-out), else a freshly generated pair (dev).\n  public static async resolve(): Promise\u003CIdentityKeyPair> {\n    const encoded = Platform.get(IdentityKeyPair.environmentVariable);\n\n    return encoded === undefined\n      ? IdentityKeyPair.generate()\n      : IdentityKeyPair.fromEncoded(encoded);\n  }\n\n  public static fromEncoded(encoded: string): IdentityKeyPair {\n    const privateKey = Base64JWK.decode(encoded);\n\n    return new IdentityKeyPair({ privateKey, publicKey: JWTVerifier.toPublicJWK(privateKey) });\n  }\n\n  public static async generate(): Promise\u003CIdentityKeyPair> {\n    return new IdentityKeyPair(await new JwkService().generate(IdentityKeyPair.algorithm));\n  }\n}\n","src\u002Fauth\u002Fidentity-key-pair.ts","ts","",[509,601,602,656,679,686,703,710,716,750,777,782,812,839,844,868,888,905,911,916,922,952,984,989,1003,1019,1041,1046,1051,1076,1100,1105,1149,1154,1159,1183,1222,1227],{"__ignoreMap":599},[603,604,607,611,615,619,622,625,628,630,633,635,638,641,644,647,650,653],"span",{"class":605,"line":606},"line",1,[603,608,610],{"class":609},"s7zQu","import",[603,612,614],{"class":613},"sMK4o"," {",[603,616,618],{"class":617},"sTEyZ"," Base64JWK",[603,620,621],{"class":613},",",[603,623,624],{"class":609}," type",[603,626,627],{"class":617}," GeneratedKeyPair",[603,629,621],{"class":613},[603,631,632],{"class":617}," JwkService",[603,634,621],{"class":613},[603,636,637],{"class":617}," JWTVerifier",[603,639,640],{"class":613}," }",[603,642,643],{"class":609}," from",[603,645,646],{"class":613}," \"",[603,648,566],{"class":649},"sfazB",[603,651,652],{"class":613},"\"",[603,654,655],{"class":613},";\n",[603,657,659,661,663,666,668,670,672,675,677],{"class":605,"line":658},2,[603,660,610],{"class":609},[603,662,614],{"class":613},[603,664,665],{"class":617}," Platform",[603,667,640],{"class":613},[603,669,643],{"class":609},[603,671,646],{"class":613},[603,673,674],{"class":649},"@heximon\u002Fruntime",[603,676,652],{"class":613},[603,678,655],{"class":613},[603,680,682],{"class":605,"line":681},3,[603,683,685],{"emptyLinePlaceholder":684},true,"\n",[603,687,689,692,696,700],{"class":605,"line":688},4,[603,690,691],{"class":609},"export",[603,693,695],{"class":694},"spNyl"," class",[603,697,699],{"class":698},"sBMFI"," IdentityKeyPair",[603,701,702],{"class":613}," {\n",[603,704,706],{"class":605,"line":705},5,[603,707,709],{"class":708},"sHwdD","  \u002F\u002F An EC keygen (ES256, P-256) is ~2ms versus an RSA-2048 keygen's tens-to-hundreds of ms, so the\n",[603,711,713],{"class":605,"line":712},6,[603,714,715],{"class":708},"  \u002F\u002F boot-time `useFactory` that resolves it stays off the critical path.\n",[603,717,719,722,725,728,732,735,738,741,743,746,748],{"class":605,"line":718},7,[603,720,721],{"class":694},"  private",[603,723,724],{"class":694}," static",[603,726,727],{"class":694}," readonly",[603,729,731],{"class":730},"swJcz"," algorithm",[603,733,734],{"class":613},":",[603,736,737],{"class":698}," string",[603,739,740],{"class":613}," =",[603,742,646],{"class":613},[603,744,745],{"class":649},"ES256",[603,747,652],{"class":613},[603,749,655],{"class":613},[603,751,753,755,757,759,762,764,766,768,770,773,775],{"class":605,"line":752},8,[603,754,721],{"class":694},[603,756,724],{"class":694},[603,758,727],{"class":694},[603,760,761],{"class":730}," environmentVariable",[603,763,734],{"class":613},[603,765,737],{"class":698},[603,767,740],{"class":613},[603,769,646],{"class":613},[603,771,772],{"class":649},"HEXIMON_JWK",[603,774,652],{"class":613},[603,776,655],{"class":613},[603,778,780],{"class":605,"line":779},9,[603,781,685],{"emptyLinePlaceholder":684},[603,783,785,788,790,793,795,797,800,802,805,807,810],{"class":605,"line":784},10,[603,786,787],{"class":694},"  public",[603,789,727],{"class":694},[603,791,792],{"class":730}," privateKey",[603,794,734],{"class":613},[603,796,627],{"class":698},[603,798,799],{"class":617},"[",[603,801,652],{"class":613},[603,803,804],{"class":649},"privateKey",[603,806,652],{"class":613},[603,808,809],{"class":617},"]",[603,811,655],{"class":613},[603,813,815,817,819,822,824,826,828,830,833,835,837],{"class":605,"line":814},11,[603,816,787],{"class":694},[603,818,727],{"class":694},[603,820,821],{"class":730}," publicKey",[603,823,734],{"class":613},[603,825,627],{"class":698},[603,827,799],{"class":617},[603,829,652],{"class":613},[603,831,832],{"class":649},"publicKey",[603,834,652],{"class":613},[603,836,809],{"class":617},[603,838,655],{"class":613},[603,840,842],{"class":605,"line":841},12,[603,843,685],{"emptyLinePlaceholder":684},[603,845,847,849,852,855,859,861,863,866],{"class":605,"line":846},13,[603,848,787],{"class":694},[603,850,851],{"class":694}," constructor",[603,853,854],{"class":613},"(",[603,856,858],{"class":857},"sHdIc","keyPair",[603,860,734],{"class":613},[603,862,627],{"class":698},[603,864,865],{"class":613},")",[603,867,702],{"class":613},[603,869,871,874,876,878,881,884,886],{"class":605,"line":870},14,[603,872,873],{"class":613},"    this.",[603,875,804],{"class":617},[603,877,740],{"class":613},[603,879,880],{"class":617}," keyPair",[603,882,883],{"class":613},".",[603,885,804],{"class":617},[603,887,655],{"class":613},[603,889,891,893,895,897,899,901,903],{"class":605,"line":890},15,[603,892,873],{"class":613},[603,894,832],{"class":617},[603,896,740],{"class":613},[603,898,880],{"class":617},[603,900,883],{"class":613},[603,902,832],{"class":617},[603,904,655],{"class":613},[603,906,908],{"class":605,"line":907},16,[603,909,910],{"class":613},"  }\n",[603,912,914],{"class":605,"line":913},17,[603,915,685],{"emptyLinePlaceholder":684},[603,917,919],{"class":605,"line":918},18,[603,920,921],{"class":708},"  \u002F\u002F HEXIMON_JWK when set (production, survives a restart\u002Fscale-out), else a freshly generated pair (dev).\n",[603,923,925,927,929,932,935,938,941,944,947,950],{"class":605,"line":924},19,[603,926,787],{"class":694},[603,928,724],{"class":694},[603,930,931],{"class":694}," async",[603,933,934],{"class":730}," resolve",[603,936,937],{"class":613},"():",[603,939,940],{"class":698}," Promise",[603,942,943],{"class":613},"\u003C",[603,945,946],{"class":698},"IdentityKeyPair",[603,948,949],{"class":613},">",[603,951,702],{"class":613},[603,953,955,958,961,963,965,967,971,973,975,977,980,982],{"class":605,"line":954},20,[603,956,957],{"class":694},"    const",[603,959,960],{"class":617}," encoded",[603,962,740],{"class":613},[603,964,665],{"class":617},[603,966,883],{"class":613},[603,968,970],{"class":969},"s2Zo4","get",[603,972,854],{"class":730},[603,974,946],{"class":617},[603,976,883],{"class":613},[603,978,979],{"class":617},"environmentVariable",[603,981,865],{"class":730},[603,983,655],{"class":613},[603,985,987],{"class":605,"line":986},21,[603,988,685],{"emptyLinePlaceholder":684},[603,990,992,995,997,1000],{"class":605,"line":991},22,[603,993,994],{"class":609},"    return",[603,996,960],{"class":617},[603,998,999],{"class":613}," ===",[603,1001,1002],{"class":613}," undefined\n",[603,1004,1006,1009,1011,1013,1016],{"class":605,"line":1005},23,[603,1007,1008],{"class":613},"      ?",[603,1010,699],{"class":617},[603,1012,883],{"class":613},[603,1014,1015],{"class":969},"generate",[603,1017,1018],{"class":730},"()\n",[603,1020,1022,1025,1027,1029,1032,1034,1037,1039],{"class":605,"line":1021},24,[603,1023,1024],{"class":613},"      :",[603,1026,699],{"class":617},[603,1028,883],{"class":613},[603,1030,1031],{"class":969},"fromEncoded",[603,1033,854],{"class":730},[603,1035,1036],{"class":617},"encoded",[603,1038,865],{"class":730},[603,1040,655],{"class":613},[603,1042,1044],{"class":605,"line":1043},25,[603,1045,910],{"class":613},[603,1047,1049],{"class":605,"line":1048},26,[603,1050,685],{"emptyLinePlaceholder":684},[603,1052,1054,1056,1058,1061,1063,1065,1067,1069,1072,1074],{"class":605,"line":1053},27,[603,1055,787],{"class":694},[603,1057,724],{"class":694},[603,1059,1060],{"class":730}," fromEncoded",[603,1062,854],{"class":613},[603,1064,1036],{"class":857},[603,1066,734],{"class":613},[603,1068,737],{"class":698},[603,1070,1071],{"class":613},"):",[603,1073,699],{"class":698},[603,1075,702],{"class":613},[603,1077,1079,1081,1083,1085,1087,1089,1092,1094,1096,1098],{"class":605,"line":1078},28,[603,1080,957],{"class":694},[603,1082,792],{"class":617},[603,1084,740],{"class":613},[603,1086,618],{"class":617},[603,1088,883],{"class":613},[603,1090,1091],{"class":969},"decode",[603,1093,854],{"class":730},[603,1095,1036],{"class":617},[603,1097,865],{"class":730},[603,1099,655],{"class":613},[603,1101,1103],{"class":605,"line":1102},29,[603,1104,685],{"emptyLinePlaceholder":684},[603,1106,1108,1110,1113,1115,1117,1120,1122,1124,1126,1128,1130,1132,1135,1137,1139,1142,1145,1147],{"class":605,"line":1107},30,[603,1109,994],{"class":609},[603,1111,1112],{"class":613}," new",[603,1114,699],{"class":969},[603,1116,854],{"class":730},[603,1118,1119],{"class":613},"{",[603,1121,792],{"class":617},[603,1123,621],{"class":613},[603,1125,821],{"class":730},[603,1127,734],{"class":613},[603,1129,637],{"class":617},[603,1131,883],{"class":613},[603,1133,1134],{"class":969},"toPublicJWK",[603,1136,854],{"class":730},[603,1138,804],{"class":617},[603,1140,1141],{"class":730},") ",[603,1143,1144],{"class":613},"}",[603,1146,865],{"class":730},[603,1148,655],{"class":613},[603,1150,1152],{"class":605,"line":1151},31,[603,1153,910],{"class":613},[603,1155,1157],{"class":605,"line":1156},32,[603,1158,685],{"emptyLinePlaceholder":684},[603,1160,1162,1164,1166,1168,1171,1173,1175,1177,1179,1181],{"class":605,"line":1161},33,[603,1163,787],{"class":694},[603,1165,724],{"class":694},[603,1167,931],{"class":694},[603,1169,1170],{"class":730}," generate",[603,1172,937],{"class":613},[603,1174,940],{"class":698},[603,1176,943],{"class":613},[603,1178,946],{"class":698},[603,1180,949],{"class":613},[603,1182,702],{"class":613},[603,1184,1186,1188,1190,1192,1194,1197,1199,1201,1204,1206,1208,1210,1212,1214,1217,1220],{"class":605,"line":1185},34,[603,1187,994],{"class":609},[603,1189,1112],{"class":613},[603,1191,699],{"class":969},[603,1193,854],{"class":730},[603,1195,1196],{"class":609},"await",[603,1198,1112],{"class":613},[603,1200,632],{"class":969},[603,1202,1203],{"class":730},"()",[603,1205,883],{"class":613},[603,1207,1015],{"class":969},[603,1209,854],{"class":730},[603,1211,946],{"class":617},[603,1213,883],{"class":613},[603,1215,1216],{"class":617},"algorithm",[603,1218,1219],{"class":730},"))",[603,1221,655],{"class":613},[603,1223,1225],{"class":605,"line":1224},35,[603,1226,910],{"class":613},[603,1228,1230],{"class":605,"line":1229},36,[603,1231,1232],{"class":613},"}\n",[499,1234,1235,1238,1239,1241,1242,1245,1246,1249,1250,1253,1254,1256,1257,1260,1261,1265],{},[509,1236,1237],{},"resolve()"," reads a Base64-encoded private JWK from the ",[509,1240,772],{}," environment variable when it's set —\n",[509,1243,1244],{},"Base64JWK.decode"," turns the one Base64 string back into a ",[509,1247,1248],{},"JWK",", and ",[509,1251,1252],{},"JWTVerifier.toPublicJWK"," derives the\npublic half by stripping its private members. With no ",[509,1255,772],{}," set, it falls back to ",[509,1258,1259],{},"generate()"," — a\nfresh pair every boot, fine for development, but a restart or a second instance (scale-out) invalidates every\nsession issued so far. In production you also publish the public half at a JWKS endpoint (see\n",[1262,1263,97],"a",{"href":1264},"\u002Fdocs\u002Fessentials\u002Fauthentication#publish-a-jwks",").",[578,1267,1269],{"id":1268},"_2-store-accounts","2. Store accounts",[499,1271,1272,1273,1275],{},"Login needs somewhere to look a user up by email and verify a password hash. A store is a plain provider\n— a class becomes injectable just by being listed in a module. A real app would back this with a database\n(see ",[1262,1274,453],{"href":454},"); an in-memory map keeps this recipe dependency-free.",[593,1277,1280],{"className":595,"code":1278,"filename":1279,"language":598,"meta":599,"style":599},"export interface NewUserAccount {\n  readonly email: string;\n  readonly passwordHash: string; \u002F\u002F the Scrypt\u002FPbkdf2-encoded hash, never the plaintext\n  readonly permissions: readonly string[];\n}\n\nexport interface UserAccount extends NewUserAccount {\n  readonly id: string;\n}\n\nexport class UserAccountStore {\n  private readonly accountsById = new Map\u003Cstring, UserAccount>();\n\n  public create(account: NewUserAccount): UserAccount {\n    const stored: UserAccount = { id: String(this.accountsById.size + 1), ...account };\n    this.accountsById.set(stored.id, stored);\n    return stored;\n  }\n\n  public findById(id: string): UserAccount | undefined {\n    return this.accountsById.get(id);\n  }\n\n  public findByEmail(email: string): UserAccount | undefined {\n    for (const account of this.accountsById.values()) {\n      if (account.email === email) return account;\n    }\n    return undefined;\n  }\n\n  public hasEmail(email: string): boolean {\n    return this.findByEmail(email) !== undefined;\n  }\n}\n","src\u002Fauth\u002Fuser-account-store.ts",[509,1281,1282,1294,1308,1325,1343,1347,1351,1367,1380,1384,1388,1399,1430,1434,1456,1510,1539,1547,1551,1555,1582,1603,1607,1611,1637,1669,1695,1700,1707,1711,1715,1737,1757,1761],{"__ignoreMap":599},[603,1283,1284,1286,1289,1292],{"class":605,"line":606},[603,1285,691],{"class":609},[603,1287,1288],{"class":694}," interface",[603,1290,1291],{"class":698}," NewUserAccount",[603,1293,702],{"class":613},[603,1295,1296,1299,1302,1304,1306],{"class":605,"line":658},[603,1297,1298],{"class":694},"  readonly",[603,1300,1301],{"class":730}," email",[603,1303,734],{"class":613},[603,1305,737],{"class":698},[603,1307,655],{"class":613},[603,1309,1310,1312,1315,1317,1319,1322],{"class":605,"line":681},[603,1311,1298],{"class":694},[603,1313,1314],{"class":730}," passwordHash",[603,1316,734],{"class":613},[603,1318,737],{"class":698},[603,1320,1321],{"class":613},";",[603,1323,1324],{"class":708}," \u002F\u002F the Scrypt\u002FPbkdf2-encoded hash, never the plaintext\n",[603,1326,1327,1329,1332,1334,1336,1338,1341],{"class":605,"line":688},[603,1328,1298],{"class":694},[603,1330,1331],{"class":730}," permissions",[603,1333,734],{"class":613},[603,1335,727],{"class":694},[603,1337,737],{"class":698},[603,1339,1340],{"class":617},"[]",[603,1342,655],{"class":613},[603,1344,1345],{"class":605,"line":705},[603,1346,1232],{"class":613},[603,1348,1349],{"class":605,"line":712},[603,1350,685],{"emptyLinePlaceholder":684},[603,1352,1353,1355,1357,1360,1363,1365],{"class":605,"line":718},[603,1354,691],{"class":609},[603,1356,1288],{"class":694},[603,1358,1359],{"class":698}," UserAccount",[603,1361,1362],{"class":694}," extends",[603,1364,1291],{"class":698},[603,1366,702],{"class":613},[603,1368,1369,1371,1374,1376,1378],{"class":605,"line":752},[603,1370,1298],{"class":694},[603,1372,1373],{"class":730}," id",[603,1375,734],{"class":613},[603,1377,737],{"class":698},[603,1379,655],{"class":613},[603,1381,1382],{"class":605,"line":779},[603,1383,1232],{"class":613},[603,1385,1386],{"class":605,"line":784},[603,1387,685],{"emptyLinePlaceholder":684},[603,1389,1390,1392,1394,1397],{"class":605,"line":814},[603,1391,691],{"class":609},[603,1393,695],{"class":694},[603,1395,1396],{"class":698}," UserAccountStore",[603,1398,702],{"class":613},[603,1400,1401,1403,1405,1408,1410,1412,1415,1417,1420,1422,1424,1426,1428],{"class":605,"line":841},[603,1402,721],{"class":694},[603,1404,727],{"class":694},[603,1406,1407],{"class":730}," accountsById",[603,1409,740],{"class":613},[603,1411,1112],{"class":613},[603,1413,1414],{"class":730}," Map",[603,1416,943],{"class":613},[603,1418,1419],{"class":698},"string",[603,1421,621],{"class":613},[603,1423,1359],{"class":698},[603,1425,949],{"class":613},[603,1427,1203],{"class":617},[603,1429,655],{"class":613},[603,1431,1432],{"class":605,"line":846},[603,1433,685],{"emptyLinePlaceholder":684},[603,1435,1436,1438,1441,1443,1446,1448,1450,1452,1454],{"class":605,"line":870},[603,1437,787],{"class":694},[603,1439,1440],{"class":730}," create",[603,1442,854],{"class":613},[603,1444,1445],{"class":857},"account",[603,1447,734],{"class":613},[603,1449,1291],{"class":698},[603,1451,1071],{"class":613},[603,1453,1359],{"class":698},[603,1455,702],{"class":613},[603,1457,1458,1460,1463,1465,1467,1469,1471,1473,1475,1478,1480,1483,1486,1488,1491,1494,1498,1500,1502,1505,1507],{"class":605,"line":890},[603,1459,957],{"class":694},[603,1461,1462],{"class":617}," stored",[603,1464,734],{"class":613},[603,1466,1359],{"class":698},[603,1468,740],{"class":613},[603,1470,614],{"class":613},[603,1472,1373],{"class":730},[603,1474,734],{"class":613},[603,1476,1477],{"class":969}," String",[603,1479,854],{"class":730},[603,1481,1482],{"class":613},"this.",[603,1484,1485],{"class":617},"accountsById",[603,1487,883],{"class":613},[603,1489,1490],{"class":617},"size",[603,1492,1493],{"class":613}," +",[603,1495,1497],{"class":1496},"sbssI"," 1",[603,1499,865],{"class":730},[603,1501,621],{"class":613},[603,1503,1504],{"class":613}," ...",[603,1506,1445],{"class":617},[603,1508,1509],{"class":613}," };\n",[603,1511,1512,1514,1516,1518,1521,1523,1526,1528,1531,1533,1535,1537],{"class":605,"line":907},[603,1513,873],{"class":613},[603,1515,1485],{"class":617},[603,1517,883],{"class":613},[603,1519,1520],{"class":969},"set",[603,1522,854],{"class":730},[603,1524,1525],{"class":617},"stored",[603,1527,883],{"class":613},[603,1529,1530],{"class":617},"id",[603,1532,621],{"class":613},[603,1534,1462],{"class":617},[603,1536,865],{"class":730},[603,1538,655],{"class":613},[603,1540,1541,1543,1545],{"class":605,"line":913},[603,1542,994],{"class":609},[603,1544,1462],{"class":617},[603,1546,655],{"class":613},[603,1548,1549],{"class":605,"line":918},[603,1550,910],{"class":613},[603,1552,1553],{"class":605,"line":924},[603,1554,685],{"emptyLinePlaceholder":684},[603,1556,1557,1559,1562,1564,1566,1568,1570,1572,1574,1577,1580],{"class":605,"line":954},[603,1558,787],{"class":694},[603,1560,1561],{"class":730}," findById",[603,1563,854],{"class":613},[603,1565,1530],{"class":857},[603,1567,734],{"class":613},[603,1569,737],{"class":698},[603,1571,1071],{"class":613},[603,1573,1359],{"class":698},[603,1575,1576],{"class":613}," |",[603,1578,1579],{"class":698}," undefined",[603,1581,702],{"class":613},[603,1583,1584,1586,1589,1591,1593,1595,1597,1599,1601],{"class":605,"line":986},[603,1585,994],{"class":609},[603,1587,1588],{"class":613}," this.",[603,1590,1485],{"class":617},[603,1592,883],{"class":613},[603,1594,970],{"class":969},[603,1596,854],{"class":730},[603,1598,1530],{"class":617},[603,1600,865],{"class":730},[603,1602,655],{"class":613},[603,1604,1605],{"class":605,"line":991},[603,1606,910],{"class":613},[603,1608,1609],{"class":605,"line":1005},[603,1610,685],{"emptyLinePlaceholder":684},[603,1612,1613,1615,1618,1620,1623,1625,1627,1629,1631,1633,1635],{"class":605,"line":1021},[603,1614,787],{"class":694},[603,1616,1617],{"class":730}," findByEmail",[603,1619,854],{"class":613},[603,1621,1622],{"class":857},"email",[603,1624,734],{"class":613},[603,1626,737],{"class":698},[603,1628,1071],{"class":613},[603,1630,1359],{"class":698},[603,1632,1576],{"class":613},[603,1634,1579],{"class":698},[603,1636,702],{"class":613},[603,1638,1639,1642,1645,1648,1651,1654,1656,1658,1660,1663,1666],{"class":605,"line":1043},[603,1640,1641],{"class":609},"    for",[603,1643,1644],{"class":730}," (",[603,1646,1647],{"class":694},"const",[603,1649,1650],{"class":617}," account",[603,1652,1653],{"class":613}," of",[603,1655,1588],{"class":613},[603,1657,1485],{"class":617},[603,1659,883],{"class":613},[603,1661,1662],{"class":969},"values",[603,1664,1665],{"class":730},"()) ",[603,1667,1668],{"class":613},"{\n",[603,1670,1671,1674,1676,1678,1680,1682,1684,1686,1688,1691,1693],{"class":605,"line":1048},[603,1672,1673],{"class":609},"      if",[603,1675,1644],{"class":730},[603,1677,1445],{"class":617},[603,1679,883],{"class":613},[603,1681,1622],{"class":617},[603,1683,999],{"class":613},[603,1685,1301],{"class":617},[603,1687,1141],{"class":730},[603,1689,1690],{"class":609},"return",[603,1692,1650],{"class":617},[603,1694,655],{"class":613},[603,1696,1697],{"class":605,"line":1053},[603,1698,1699],{"class":613},"    }\n",[603,1701,1702,1704],{"class":605,"line":1078},[603,1703,994],{"class":609},[603,1705,1706],{"class":613}," undefined;\n",[603,1708,1709],{"class":605,"line":1102},[603,1710,910],{"class":613},[603,1712,1713],{"class":605,"line":1107},[603,1714,685],{"emptyLinePlaceholder":684},[603,1716,1717,1719,1722,1724,1726,1728,1730,1732,1735],{"class":605,"line":1151},[603,1718,787],{"class":694},[603,1720,1721],{"class":730}," hasEmail",[603,1723,854],{"class":613},[603,1725,1622],{"class":857},[603,1727,734],{"class":613},[603,1729,737],{"class":698},[603,1731,1071],{"class":613},[603,1733,1734],{"class":698}," boolean",[603,1736,702],{"class":613},[603,1738,1739,1741,1743,1746,1748,1750,1752,1755],{"class":605,"line":1156},[603,1740,994],{"class":609},[603,1742,1588],{"class":613},[603,1744,1745],{"class":969},"findByEmail",[603,1747,854],{"class":730},[603,1749,1622],{"class":617},[603,1751,1141],{"class":730},[603,1753,1754],{"class":613},"!==",[603,1756,1706],{"class":613},[603,1758,1759],{"class":605,"line":1161},[603,1760,910],{"class":613},[603,1762,1763],{"class":605,"line":1185},[603,1764,1232],{"class":613},[578,1766,1768],{"id":1767},"_3-issue-tokens-on-login","3. Issue tokens on login",[499,1770,1771,1774,1775,1778,1779,1782],{},[509,1772,1773],{},"register"," hashes the password and grants the default scope. ",[509,1776,1777],{},"login"," verifies the password in constant\ntime, then signs a payload carrying the account's scopes — they travel inside the token, so the verifier\nneeds no database lookup later. ",[509,1780,1781],{},"me"," reads the principal the middleware established for the request.",[499,1784,1785,1786,1789,1790,1793,1794,1797,1798,1800],{},"The controller injects the abstract ",[509,1787,1788],{},"PasswordHashingAlgorithm"," port (not a concrete ",[509,1791,1792],{},"Scrypt","), so an edge\nbuild can swap in ",[509,1795,1796],{},"Pbkdf2"," with no controller change, and the shipped ",[509,1799,538],{}," runs at the\ncontroller level — its constructor is plain DI tokens, so it's listed directly, no app-owned wrapper needed.",[593,1802,1805],{"className":595,"code":1803,"filename":1804,"language":598,"meta":599,"style":599},"import { AuthContext, JWTAuthMiddleware, JWTFactory, PasswordHashingAlgorithm } from \"@heximon\u002Fauth\";\nimport type { Controller, Get, Post } from \"@heximon\u002Fhttp\";\nimport type { UserAccount } from \".\u002Fuser-account-store\";\nimport { UserAccountStore } from \".\u002Fuser-account-store\";\n\ninterface Credentials {\n  readonly email: string;\n  readonly password: string;\n}\n\nexport class AuthController implements Controller\u003C{ prefix: \"\u002Fauth\"; middlewares: [JWTAuthMiddleware] }> {\n  private static readonly minimumPasswordLength: number = 8;\n  private static readonly tokenLifetimeMilliseconds: number = 60 * 60 * 1000; \u002F\u002F one hour\n  private static readonly defaultPermissions: readonly string[] = [\"profile:read\"];\n\n  public constructor(\n    private readonly accounts: UserAccountStore,\n    private readonly passwordHashing: PasswordHashingAlgorithm,\n    private readonly tokenFactory: JWTFactory,\n    private readonly authContext: AuthContext,\n  ) {}\n\n  public async register(action: Post\u003C\"\u002Fregister\">): Promise\u003CResponse> {\n    const credentials = AuthController.readCredentials(await action.request.readBody());\n\n    if (!credentials) {\n      return action.respond(400, { error: \"An email and an 8+ character password are required.\" });\n    }\n    if (this.accounts.hasEmail(credentials.email)) {\n      return action.respond(409, { error: \"That email is already registered.\" });\n    }\n\n    const account = this.accounts.create({\n      email: credentials.email,\n      passwordHash: await this.passwordHashing.hash(credentials.password),\n      permissions: AuthController.defaultPermissions,\n    });\n\n    return action.respond(201, { id: account.id, email: account.email });\n  }\n\n  public async login(action: Post\u003C\"\u002Flogin\">): Promise\u003CResponse> {\n    const credentials = AuthController.readCredentials(await action.request.readBody());\n\n    if (!credentials) {\n      return action.respond(400, { error: \"An email and a password are required.\" });\n    }\n\n    const account = this.accounts.findByEmail(credentials.email);\n\n    if (!account) {\n      \u002F\u002F Run a dummy hash so a missing account is indistinguishable (by timing) from a wrong password.\n      await this.passwordHashing.simulate();\n      return action.respond(401, { error: \"Invalid credentials.\" });\n    }\n    if (!(await this.passwordHashing.verify(account.passwordHash, credentials.password))) {\n      return action.respond(401, { error: \"Invalid credentials.\" });\n    }\n\n    const expiresAt = new Date(Date.now() + AuthController.tokenLifetimeMilliseconds);\n    const token = await this.tokenFactory.create(\n      { sub: account.id, permissions: [...account.permissions] },\n      expiresAt,\n    );\n\n    \u002F\u002F Deliver the token both ways: an HttpOnly cookie the middleware reads back, and the body for an\n    \u002F\u002F `Authorization: Bearer` client (curl, the test suite, another service).\n    action.response.setCookie(\"accessToken\", token.value, {\n      httpOnly: true,\n      sameSite: \"lax\",\n      secure: false,\n      expires: expiresAt,\n    });\n\n    return action.respond(200, {\n      userId: account.id,\n      accessToken: token.value,\n      expiresAt: expiresAt.toISOString(),\n    });\n  }\n\n  public async me(action: Get\u003C\"\u002Fme\">): Promise\u003CResponse> {\n    const userId = this.authContext.subject(); \u002F\u002F set by JWTAuthMiddleware when a valid token was sent\n\n    if (userId === undefined) {\n      return action.respond(401, { error: \"Authentication is required.\" });\n    }\n\n    const account = this.accounts.findById(userId);\n\n    return account === undefined\n      ? action.respond(401, { error: \"The authenticated account no longer exists.\" })\n      : action.respond(200, AuthController.toProfile(account));\n  }\n\n  private static readCredentials(body: unknown): Credentials | undefined {\n    if (typeof body !== \"object\" || body === null) return undefined;\n    const { email, password } = body as Record\u003Cstring, unknown>;\n    if (typeof email !== \"string\" || !email.includes(\"@\")) return undefined;\n    if (typeof password !== \"string\" || password.length \u003C AuthController.minimumPasswordLength) {\n      return undefined;\n    }\n    return { email, password };\n  }\n\n  private static toProfile(account: UserAccount): { id: string; email: string; permissions: readonly string[] } {\n    return { id: account.id, email: account.email, permissions: account.permissions };\n  }\n}\n","src\u002Fauth\u002Fauth.controller.ts",[509,1806,1807,1843,1877,1900,1920,1924,1934,1946,1959,1963,1967,2016,2039,2074,2110,2114,2123,2140,2155,2170,2185,2193,2197,2238,2276,2280,2297,2336,2340,2369,2405,2409,2413,2434,2449,2482,2498,2508,2513,2561,2566,2571,2610,2643,2648,2663,2699,2704,2709,2738,2743,2758,2764,2783,2819,2824,2868,2903,2908,2913,2955,2978,3018,3026,3034,3039,3045,3051,3089,3103,3120,3133,3145,3154,3159,3178,3194,3210,3228,3237,3242,3247,3286,3313,3318,3336,3372,3377,3382,3408,3413,3424,3459,3491,3496,3501,3531,3570,3606,3652,3694,3701,3706,3721,3726,3731,3783,3826,3831],{"__ignoreMap":599},[603,1808,1809,1811,1813,1816,1818,1821,1823,1826,1828,1831,1833,1835,1837,1839,1841],{"class":605,"line":606},[603,1810,610],{"class":609},[603,1812,614],{"class":613},[603,1814,1815],{"class":617}," AuthContext",[603,1817,621],{"class":613},[603,1819,1820],{"class":617}," JWTAuthMiddleware",[603,1822,621],{"class":613},[603,1824,1825],{"class":617}," JWTFactory",[603,1827,621],{"class":613},[603,1829,1830],{"class":617}," PasswordHashingAlgorithm",[603,1832,640],{"class":613},[603,1834,643],{"class":609},[603,1836,646],{"class":613},[603,1838,566],{"class":649},[603,1840,652],{"class":613},[603,1842,655],{"class":613},[603,1844,1845,1847,1849,1851,1854,1856,1859,1861,1864,1866,1868,1870,1873,1875],{"class":605,"line":658},[603,1846,610],{"class":609},[603,1848,624],{"class":609},[603,1850,614],{"class":613},[603,1852,1853],{"class":617}," Controller",[603,1855,621],{"class":613},[603,1857,1858],{"class":617}," Get",[603,1860,621],{"class":613},[603,1862,1863],{"class":617}," Post",[603,1865,640],{"class":613},[603,1867,643],{"class":609},[603,1869,646],{"class":613},[603,1871,1872],{"class":649},"@heximon\u002Fhttp",[603,1874,652],{"class":613},[603,1876,655],{"class":613},[603,1878,1879,1881,1883,1885,1887,1889,1891,1893,1896,1898],{"class":605,"line":681},[603,1880,610],{"class":609},[603,1882,624],{"class":609},[603,1884,614],{"class":613},[603,1886,1359],{"class":617},[603,1888,640],{"class":613},[603,1890,643],{"class":609},[603,1892,646],{"class":613},[603,1894,1895],{"class":649},".\u002Fuser-account-store",[603,1897,652],{"class":613},[603,1899,655],{"class":613},[603,1901,1902,1904,1906,1908,1910,1912,1914,1916,1918],{"class":605,"line":688},[603,1903,610],{"class":609},[603,1905,614],{"class":613},[603,1907,1396],{"class":617},[603,1909,640],{"class":613},[603,1911,643],{"class":609},[603,1913,646],{"class":613},[603,1915,1895],{"class":649},[603,1917,652],{"class":613},[603,1919,655],{"class":613},[603,1921,1922],{"class":605,"line":705},[603,1923,685],{"emptyLinePlaceholder":684},[603,1925,1926,1929,1932],{"class":605,"line":712},[603,1927,1928],{"class":694},"interface",[603,1930,1931],{"class":698}," Credentials",[603,1933,702],{"class":613},[603,1935,1936,1938,1940,1942,1944],{"class":605,"line":718},[603,1937,1298],{"class":694},[603,1939,1301],{"class":730},[603,1941,734],{"class":613},[603,1943,737],{"class":698},[603,1945,655],{"class":613},[603,1947,1948,1950,1953,1955,1957],{"class":605,"line":752},[603,1949,1298],{"class":694},[603,1951,1952],{"class":730}," password",[603,1954,734],{"class":613},[603,1956,737],{"class":698},[603,1958,655],{"class":613},[603,1960,1961],{"class":605,"line":779},[603,1962,1232],{"class":613},[603,1964,1965],{"class":605,"line":784},[603,1966,685],{"emptyLinePlaceholder":684},[603,1968,1969,1971,1973,1976,1979,1981,1984,1987,1989,1991,1994,1996,1998,2001,2003,2006,2008,2011,2014],{"class":605,"line":814},[603,1970,691],{"class":609},[603,1972,695],{"class":694},[603,1974,1975],{"class":698}," AuthController",[603,1977,1978],{"class":694}," implements",[603,1980,1853],{"class":698},[603,1982,1983],{"class":613},"\u003C{",[603,1985,1986],{"class":730}," prefix",[603,1988,734],{"class":613},[603,1990,646],{"class":613},[603,1992,1993],{"class":649},"\u002Fauth",[603,1995,652],{"class":613},[603,1997,1321],{"class":613},[603,1999,2000],{"class":730}," middlewares",[603,2002,734],{"class":613},[603,2004,2005],{"class":617}," [",[603,2007,538],{"class":698},[603,2009,2010],{"class":617},"] ",[603,2012,2013],{"class":613},"}>",[603,2015,702],{"class":613},[603,2017,2018,2020,2022,2024,2027,2029,2032,2034,2037],{"class":605,"line":841},[603,2019,721],{"class":694},[603,2021,724],{"class":694},[603,2023,727],{"class":694},[603,2025,2026],{"class":730}," minimumPasswordLength",[603,2028,734],{"class":613},[603,2030,2031],{"class":698}," number",[603,2033,740],{"class":613},[603,2035,2036],{"class":1496}," 8",[603,2038,655],{"class":613},[603,2040,2041,2043,2045,2047,2050,2052,2054,2056,2059,2062,2064,2066,2069,2071],{"class":605,"line":846},[603,2042,721],{"class":694},[603,2044,724],{"class":694},[603,2046,727],{"class":694},[603,2048,2049],{"class":730}," tokenLifetimeMilliseconds",[603,2051,734],{"class":613},[603,2053,2031],{"class":698},[603,2055,740],{"class":613},[603,2057,2058],{"class":1496}," 60",[603,2060,2061],{"class":613}," *",[603,2063,2058],{"class":1496},[603,2065,2061],{"class":613},[603,2067,2068],{"class":1496}," 1000",[603,2070,1321],{"class":613},[603,2072,2073],{"class":708}," \u002F\u002F one hour\n",[603,2075,2076,2078,2080,2082,2085,2087,2089,2091,2094,2097,2099,2101,2104,2106,2108],{"class":605,"line":870},[603,2077,721],{"class":694},[603,2079,724],{"class":694},[603,2081,727],{"class":694},[603,2083,2084],{"class":730}," defaultPermissions",[603,2086,734],{"class":613},[603,2088,727],{"class":694},[603,2090,737],{"class":698},[603,2092,2093],{"class":617},"[] ",[603,2095,2096],{"class":613},"=",[603,2098,2005],{"class":617},[603,2100,652],{"class":613},[603,2102,2103],{"class":649},"profile:read",[603,2105,652],{"class":613},[603,2107,809],{"class":617},[603,2109,655],{"class":613},[603,2111,2112],{"class":605,"line":890},[603,2113,685],{"emptyLinePlaceholder":684},[603,2115,2116,2118,2120],{"class":605,"line":907},[603,2117,787],{"class":694},[603,2119,851],{"class":694},[603,2121,2122],{"class":613},"(\n",[603,2124,2125,2128,2130,2133,2135,2137],{"class":605,"line":913},[603,2126,2127],{"class":694},"    private",[603,2129,727],{"class":694},[603,2131,2132],{"class":857}," accounts",[603,2134,734],{"class":613},[603,2136,1396],{"class":698},[603,2138,2139],{"class":613},",\n",[603,2141,2142,2144,2146,2149,2151,2153],{"class":605,"line":918},[603,2143,2127],{"class":694},[603,2145,727],{"class":694},[603,2147,2148],{"class":857}," passwordHashing",[603,2150,734],{"class":613},[603,2152,1830],{"class":698},[603,2154,2139],{"class":613},[603,2156,2157,2159,2161,2164,2166,2168],{"class":605,"line":924},[603,2158,2127],{"class":694},[603,2160,727],{"class":694},[603,2162,2163],{"class":857}," tokenFactory",[603,2165,734],{"class":613},[603,2167,1825],{"class":698},[603,2169,2139],{"class":613},[603,2171,2172,2174,2176,2179,2181,2183],{"class":605,"line":954},[603,2173,2127],{"class":694},[603,2175,727],{"class":694},[603,2177,2178],{"class":857}," authContext",[603,2180,734],{"class":613},[603,2182,1815],{"class":698},[603,2184,2139],{"class":613},[603,2186,2187,2190],{"class":605,"line":986},[603,2188,2189],{"class":613},"  )",[603,2191,2192],{"class":613}," {}\n",[603,2194,2195],{"class":605,"line":991},[603,2196,685],{"emptyLinePlaceholder":684},[603,2198,2199,2201,2203,2206,2208,2211,2213,2215,2217,2219,2222,2224,2227,2229,2231,2234,2236],{"class":605,"line":1005},[603,2200,787],{"class":694},[603,2202,931],{"class":694},[603,2204,2205],{"class":730}," register",[603,2207,854],{"class":613},[603,2209,2210],{"class":857},"action",[603,2212,734],{"class":613},[603,2214,1863],{"class":698},[603,2216,943],{"class":613},[603,2218,652],{"class":613},[603,2220,2221],{"class":649},"\u002Fregister",[603,2223,652],{"class":613},[603,2225,2226],{"class":613},">):",[603,2228,940],{"class":698},[603,2230,943],{"class":613},[603,2232,2233],{"class":698},"Response",[603,2235,949],{"class":613},[603,2237,702],{"class":613},[603,2239,2240,2242,2245,2247,2249,2251,2254,2256,2258,2261,2263,2266,2268,2271,2274],{"class":605,"line":1021},[603,2241,957],{"class":694},[603,2243,2244],{"class":617}," credentials",[603,2246,740],{"class":613},[603,2248,1975],{"class":617},[603,2250,883],{"class":613},[603,2252,2253],{"class":969},"readCredentials",[603,2255,854],{"class":730},[603,2257,1196],{"class":609},[603,2259,2260],{"class":617}," action",[603,2262,883],{"class":613},[603,2264,2265],{"class":617},"request",[603,2267,883],{"class":613},[603,2269,2270],{"class":969},"readBody",[603,2272,2273],{"class":730},"())",[603,2275,655],{"class":613},[603,2277,2278],{"class":605,"line":1043},[603,2279,685],{"emptyLinePlaceholder":684},[603,2281,2282,2285,2287,2290,2293,2295],{"class":605,"line":1048},[603,2283,2284],{"class":609},"    if",[603,2286,1644],{"class":730},[603,2288,2289],{"class":613},"!",[603,2291,2292],{"class":617},"credentials",[603,2294,1141],{"class":730},[603,2296,1668],{"class":613},[603,2298,2299,2302,2304,2306,2309,2311,2314,2316,2318,2321,2323,2325,2328,2330,2332,2334],{"class":605,"line":1053},[603,2300,2301],{"class":609},"      return",[603,2303,2260],{"class":617},[603,2305,883],{"class":613},[603,2307,2308],{"class":969},"respond",[603,2310,854],{"class":730},[603,2312,2313],{"class":1496},"400",[603,2315,621],{"class":613},[603,2317,614],{"class":613},[603,2319,2320],{"class":730}," error",[603,2322,734],{"class":613},[603,2324,646],{"class":613},[603,2326,2327],{"class":649},"An email and an 8+ character password are required.",[603,2329,652],{"class":613},[603,2331,640],{"class":613},[603,2333,865],{"class":730},[603,2335,655],{"class":613},[603,2337,2338],{"class":605,"line":1078},[603,2339,1699],{"class":613},[603,2341,2342,2344,2346,2348,2351,2353,2356,2358,2360,2362,2364,2367],{"class":605,"line":1102},[603,2343,2284],{"class":609},[603,2345,1644],{"class":730},[603,2347,1482],{"class":613},[603,2349,2350],{"class":617},"accounts",[603,2352,883],{"class":613},[603,2354,2355],{"class":969},"hasEmail",[603,2357,854],{"class":730},[603,2359,2292],{"class":617},[603,2361,883],{"class":613},[603,2363,1622],{"class":617},[603,2365,2366],{"class":730},")) ",[603,2368,1668],{"class":613},[603,2370,2371,2373,2375,2377,2379,2381,2384,2386,2388,2390,2392,2394,2397,2399,2401,2403],{"class":605,"line":1107},[603,2372,2301],{"class":609},[603,2374,2260],{"class":617},[603,2376,883],{"class":613},[603,2378,2308],{"class":969},[603,2380,854],{"class":730},[603,2382,2383],{"class":1496},"409",[603,2385,621],{"class":613},[603,2387,614],{"class":613},[603,2389,2320],{"class":730},[603,2391,734],{"class":613},[603,2393,646],{"class":613},[603,2395,2396],{"class":649},"That email is already registered.",[603,2398,652],{"class":613},[603,2400,640],{"class":613},[603,2402,865],{"class":730},[603,2404,655],{"class":613},[603,2406,2407],{"class":605,"line":1151},[603,2408,1699],{"class":613},[603,2410,2411],{"class":605,"line":1156},[603,2412,685],{"emptyLinePlaceholder":684},[603,2414,2415,2417,2419,2421,2423,2425,2427,2430,2432],{"class":605,"line":1161},[603,2416,957],{"class":694},[603,2418,1650],{"class":617},[603,2420,740],{"class":613},[603,2422,1588],{"class":613},[603,2424,2350],{"class":617},[603,2426,883],{"class":613},[603,2428,2429],{"class":969},"create",[603,2431,854],{"class":730},[603,2433,1668],{"class":613},[603,2435,2436,2439,2441,2443,2445,2447],{"class":605,"line":1185},[603,2437,2438],{"class":730},"      email",[603,2440,734],{"class":613},[603,2442,2244],{"class":617},[603,2444,883],{"class":613},[603,2446,1622],{"class":617},[603,2448,2139],{"class":613},[603,2450,2451,2454,2456,2459,2461,2464,2466,2469,2471,2473,2475,2478,2480],{"class":605,"line":1224},[603,2452,2453],{"class":730},"      passwordHash",[603,2455,734],{"class":613},[603,2457,2458],{"class":609}," await",[603,2460,1588],{"class":613},[603,2462,2463],{"class":617},"passwordHashing",[603,2465,883],{"class":613},[603,2467,2468],{"class":969},"hash",[603,2470,854],{"class":730},[603,2472,2292],{"class":617},[603,2474,883],{"class":613},[603,2476,2477],{"class":617},"password",[603,2479,865],{"class":730},[603,2481,2139],{"class":613},[603,2483,2484,2487,2489,2491,2493,2496],{"class":605,"line":1229},[603,2485,2486],{"class":730},"      permissions",[603,2488,734],{"class":613},[603,2490,1975],{"class":617},[603,2492,883],{"class":613},[603,2494,2495],{"class":617},"defaultPermissions",[603,2497,2139],{"class":613},[603,2499,2501,2504,2506],{"class":605,"line":2500},37,[603,2502,2503],{"class":613},"    }",[603,2505,865],{"class":730},[603,2507,655],{"class":613},[603,2509,2511],{"class":605,"line":2510},38,[603,2512,685],{"emptyLinePlaceholder":684},[603,2514,2516,2518,2520,2522,2524,2526,2529,2531,2533,2535,2537,2539,2541,2543,2545,2547,2549,2551,2553,2555,2557,2559],{"class":605,"line":2515},39,[603,2517,994],{"class":609},[603,2519,2260],{"class":617},[603,2521,883],{"class":613},[603,2523,2308],{"class":969},[603,2525,854],{"class":730},[603,2527,2528],{"class":1496},"201",[603,2530,621],{"class":613},[603,2532,614],{"class":613},[603,2534,1373],{"class":730},[603,2536,734],{"class":613},[603,2538,1650],{"class":617},[603,2540,883],{"class":613},[603,2542,1530],{"class":617},[603,2544,621],{"class":613},[603,2546,1301],{"class":730},[603,2548,734],{"class":613},[603,2550,1650],{"class":617},[603,2552,883],{"class":613},[603,2554,1622],{"class":617},[603,2556,640],{"class":613},[603,2558,865],{"class":730},[603,2560,655],{"class":613},[603,2562,2564],{"class":605,"line":2563},40,[603,2565,910],{"class":613},[603,2567,2569],{"class":605,"line":2568},41,[603,2570,685],{"emptyLinePlaceholder":684},[603,2572,2574,2576,2578,2581,2583,2585,2587,2589,2591,2593,2596,2598,2600,2602,2604,2606,2608],{"class":605,"line":2573},42,[603,2575,787],{"class":694},[603,2577,931],{"class":694},[603,2579,2580],{"class":730}," login",[603,2582,854],{"class":613},[603,2584,2210],{"class":857},[603,2586,734],{"class":613},[603,2588,1863],{"class":698},[603,2590,943],{"class":613},[603,2592,652],{"class":613},[603,2594,2595],{"class":649},"\u002Flogin",[603,2597,652],{"class":613},[603,2599,2226],{"class":613},[603,2601,940],{"class":698},[603,2603,943],{"class":613},[603,2605,2233],{"class":698},[603,2607,949],{"class":613},[603,2609,702],{"class":613},[603,2611,2613,2615,2617,2619,2621,2623,2625,2627,2629,2631,2633,2635,2637,2639,2641],{"class":605,"line":2612},43,[603,2614,957],{"class":694},[603,2616,2244],{"class":617},[603,2618,740],{"class":613},[603,2620,1975],{"class":617},[603,2622,883],{"class":613},[603,2624,2253],{"class":969},[603,2626,854],{"class":730},[603,2628,1196],{"class":609},[603,2630,2260],{"class":617},[603,2632,883],{"class":613},[603,2634,2265],{"class":617},[603,2636,883],{"class":613},[603,2638,2270],{"class":969},[603,2640,2273],{"class":730},[603,2642,655],{"class":613},[603,2644,2646],{"class":605,"line":2645},44,[603,2647,685],{"emptyLinePlaceholder":684},[603,2649,2651,2653,2655,2657,2659,2661],{"class":605,"line":2650},45,[603,2652,2284],{"class":609},[603,2654,1644],{"class":730},[603,2656,2289],{"class":613},[603,2658,2292],{"class":617},[603,2660,1141],{"class":730},[603,2662,1668],{"class":613},[603,2664,2666,2668,2670,2672,2674,2676,2678,2680,2682,2684,2686,2688,2691,2693,2695,2697],{"class":605,"line":2665},46,[603,2667,2301],{"class":609},[603,2669,2260],{"class":617},[603,2671,883],{"class":613},[603,2673,2308],{"class":969},[603,2675,854],{"class":730},[603,2677,2313],{"class":1496},[603,2679,621],{"class":613},[603,2681,614],{"class":613},[603,2683,2320],{"class":730},[603,2685,734],{"class":613},[603,2687,646],{"class":613},[603,2689,2690],{"class":649},"An email and a password are required.",[603,2692,652],{"class":613},[603,2694,640],{"class":613},[603,2696,865],{"class":730},[603,2698,655],{"class":613},[603,2700,2702],{"class":605,"line":2701},47,[603,2703,1699],{"class":613},[603,2705,2707],{"class":605,"line":2706},48,[603,2708,685],{"emptyLinePlaceholder":684},[603,2710,2712,2714,2716,2718,2720,2722,2724,2726,2728,2730,2732,2734,2736],{"class":605,"line":2711},49,[603,2713,957],{"class":694},[603,2715,1650],{"class":617},[603,2717,740],{"class":613},[603,2719,1588],{"class":613},[603,2721,2350],{"class":617},[603,2723,883],{"class":613},[603,2725,1745],{"class":969},[603,2727,854],{"class":730},[603,2729,2292],{"class":617},[603,2731,883],{"class":613},[603,2733,1622],{"class":617},[603,2735,865],{"class":730},[603,2737,655],{"class":613},[603,2739,2741],{"class":605,"line":2740},50,[603,2742,685],{"emptyLinePlaceholder":684},[603,2744,2746,2748,2750,2752,2754,2756],{"class":605,"line":2745},51,[603,2747,2284],{"class":609},[603,2749,1644],{"class":730},[603,2751,2289],{"class":613},[603,2753,1445],{"class":617},[603,2755,1141],{"class":730},[603,2757,1668],{"class":613},[603,2759,2761],{"class":605,"line":2760},52,[603,2762,2763],{"class":708},"      \u002F\u002F Run a dummy hash so a missing account is indistinguishable (by timing) from a wrong password.\n",[603,2765,2767,2770,2772,2774,2776,2779,2781],{"class":605,"line":2766},53,[603,2768,2769],{"class":609},"      await",[603,2771,1588],{"class":613},[603,2773,2463],{"class":617},[603,2775,883],{"class":613},[603,2777,2778],{"class":969},"simulate",[603,2780,1203],{"class":730},[603,2782,655],{"class":613},[603,2784,2786,2788,2790,2792,2794,2796,2798,2800,2802,2804,2806,2808,2811,2813,2815,2817],{"class":605,"line":2785},54,[603,2787,2301],{"class":609},[603,2789,2260],{"class":617},[603,2791,883],{"class":613},[603,2793,2308],{"class":969},[603,2795,854],{"class":730},[603,2797,551],{"class":1496},[603,2799,621],{"class":613},[603,2801,614],{"class":613},[603,2803,2320],{"class":730},[603,2805,734],{"class":613},[603,2807,646],{"class":613},[603,2809,2810],{"class":649},"Invalid credentials.",[603,2812,652],{"class":613},[603,2814,640],{"class":613},[603,2816,865],{"class":730},[603,2818,655],{"class":613},[603,2820,2822],{"class":605,"line":2821},55,[603,2823,1699],{"class":613},[603,2825,2827,2829,2831,2833,2835,2837,2839,2841,2843,2846,2848,2850,2852,2855,2857,2859,2861,2863,2866],{"class":605,"line":2826},56,[603,2828,2284],{"class":609},[603,2830,1644],{"class":730},[603,2832,2289],{"class":613},[603,2834,854],{"class":730},[603,2836,1196],{"class":609},[603,2838,1588],{"class":613},[603,2840,2463],{"class":617},[603,2842,883],{"class":613},[603,2844,2845],{"class":969},"verify",[603,2847,854],{"class":730},[603,2849,1445],{"class":617},[603,2851,883],{"class":613},[603,2853,2854],{"class":617},"passwordHash",[603,2856,621],{"class":613},[603,2858,2244],{"class":617},[603,2860,883],{"class":613},[603,2862,2477],{"class":617},[603,2864,2865],{"class":730},"))) ",[603,2867,1668],{"class":613},[603,2869,2871,2873,2875,2877,2879,2881,2883,2885,2887,2889,2891,2893,2895,2897,2899,2901],{"class":605,"line":2870},57,[603,2872,2301],{"class":609},[603,2874,2260],{"class":617},[603,2876,883],{"class":613},[603,2878,2308],{"class":969},[603,2880,854],{"class":730},[603,2882,551],{"class":1496},[603,2884,621],{"class":613},[603,2886,614],{"class":613},[603,2888,2320],{"class":730},[603,2890,734],{"class":613},[603,2892,646],{"class":613},[603,2894,2810],{"class":649},[603,2896,652],{"class":613},[603,2898,640],{"class":613},[603,2900,865],{"class":730},[603,2902,655],{"class":613},[603,2904,2906],{"class":605,"line":2905},58,[603,2907,1699],{"class":613},[603,2909,2911],{"class":605,"line":2910},59,[603,2912,685],{"emptyLinePlaceholder":684},[603,2914,2916,2918,2921,2923,2925,2928,2930,2933,2935,2938,2941,2944,2946,2948,2951,2953],{"class":605,"line":2915},60,[603,2917,957],{"class":694},[603,2919,2920],{"class":617}," expiresAt",[603,2922,740],{"class":613},[603,2924,1112],{"class":613},[603,2926,2927],{"class":969}," Date",[603,2929,854],{"class":730},[603,2931,2932],{"class":617},"Date",[603,2934,883],{"class":613},[603,2936,2937],{"class":969},"now",[603,2939,2940],{"class":730},"() ",[603,2942,2943],{"class":613},"+",[603,2945,1975],{"class":617},[603,2947,883],{"class":613},[603,2949,2950],{"class":617},"tokenLifetimeMilliseconds",[603,2952,865],{"class":730},[603,2954,655],{"class":613},[603,2956,2958,2960,2963,2965,2967,2969,2972,2974,2976],{"class":605,"line":2957},61,[603,2959,957],{"class":694},[603,2961,2962],{"class":617}," token",[603,2964,740],{"class":613},[603,2966,2458],{"class":609},[603,2968,1588],{"class":613},[603,2970,2971],{"class":617},"tokenFactory",[603,2973,883],{"class":613},[603,2975,2429],{"class":969},[603,2977,2122],{"class":730},[603,2979,2981,2984,2987,2989,2991,2993,2995,2997,2999,3001,3003,3006,3008,3010,3013,3015],{"class":605,"line":2980},62,[603,2982,2983],{"class":613},"      {",[603,2985,2986],{"class":730}," sub",[603,2988,734],{"class":613},[603,2990,1650],{"class":617},[603,2992,883],{"class":613},[603,2994,1530],{"class":617},[603,2996,621],{"class":613},[603,2998,1331],{"class":730},[603,3000,734],{"class":613},[603,3002,2005],{"class":730},[603,3004,3005],{"class":613},"...",[603,3007,1445],{"class":617},[603,3009,883],{"class":613},[603,3011,3012],{"class":617},"permissions",[603,3014,2010],{"class":730},[603,3016,3017],{"class":613},"},\n",[603,3019,3021,3024],{"class":605,"line":3020},63,[603,3022,3023],{"class":617},"      expiresAt",[603,3025,2139],{"class":613},[603,3027,3029,3032],{"class":605,"line":3028},64,[603,3030,3031],{"class":730},"    )",[603,3033,655],{"class":613},[603,3035,3037],{"class":605,"line":3036},65,[603,3038,685],{"emptyLinePlaceholder":684},[603,3040,3042],{"class":605,"line":3041},66,[603,3043,3044],{"class":708},"    \u002F\u002F Deliver the token both ways: an HttpOnly cookie the middleware reads back, and the body for an\n",[603,3046,3048],{"class":605,"line":3047},67,[603,3049,3050],{"class":708},"    \u002F\u002F `Authorization: Bearer` client (curl, the test suite, another service).\n",[603,3052,3054,3057,3059,3062,3064,3067,3069,3071,3074,3076,3078,3080,3082,3085,3087],{"class":605,"line":3053},68,[603,3055,3056],{"class":617},"    action",[603,3058,883],{"class":613},[603,3060,3061],{"class":617},"response",[603,3063,883],{"class":613},[603,3065,3066],{"class":969},"setCookie",[603,3068,854],{"class":730},[603,3070,652],{"class":613},[603,3072,3073],{"class":649},"accessToken",[603,3075,652],{"class":613},[603,3077,621],{"class":613},[603,3079,2962],{"class":617},[603,3081,883],{"class":613},[603,3083,3084],{"class":617},"value",[603,3086,621],{"class":613},[603,3088,702],{"class":613},[603,3090,3092,3095,3097,3101],{"class":605,"line":3091},69,[603,3093,3094],{"class":730},"      httpOnly",[603,3096,734],{"class":613},[603,3098,3100],{"class":3099},"sfNiH"," true",[603,3102,2139],{"class":613},[603,3104,3106,3109,3111,3113,3116,3118],{"class":605,"line":3105},70,[603,3107,3108],{"class":730},"      sameSite",[603,3110,734],{"class":613},[603,3112,646],{"class":613},[603,3114,3115],{"class":649},"lax",[603,3117,652],{"class":613},[603,3119,2139],{"class":613},[603,3121,3123,3126,3128,3131],{"class":605,"line":3122},71,[603,3124,3125],{"class":730},"      secure",[603,3127,734],{"class":613},[603,3129,3130],{"class":3099}," false",[603,3132,2139],{"class":613},[603,3134,3136,3139,3141,3143],{"class":605,"line":3135},72,[603,3137,3138],{"class":730},"      expires",[603,3140,734],{"class":613},[603,3142,2920],{"class":617},[603,3144,2139],{"class":613},[603,3146,3148,3150,3152],{"class":605,"line":3147},73,[603,3149,2503],{"class":613},[603,3151,865],{"class":730},[603,3153,655],{"class":613},[603,3155,3157],{"class":605,"line":3156},74,[603,3158,685],{"emptyLinePlaceholder":684},[603,3160,3162,3164,3166,3168,3170,3172,3174,3176],{"class":605,"line":3161},75,[603,3163,994],{"class":609},[603,3165,2260],{"class":617},[603,3167,883],{"class":613},[603,3169,2308],{"class":969},[603,3171,854],{"class":730},[603,3173,559],{"class":1496},[603,3175,621],{"class":613},[603,3177,702],{"class":613},[603,3179,3181,3184,3186,3188,3190,3192],{"class":605,"line":3180},76,[603,3182,3183],{"class":730},"      userId",[603,3185,734],{"class":613},[603,3187,1650],{"class":617},[603,3189,883],{"class":613},[603,3191,1530],{"class":617},[603,3193,2139],{"class":613},[603,3195,3197,3200,3202,3204,3206,3208],{"class":605,"line":3196},77,[603,3198,3199],{"class":730},"      accessToken",[603,3201,734],{"class":613},[603,3203,2962],{"class":617},[603,3205,883],{"class":613},[603,3207,3084],{"class":617},[603,3209,2139],{"class":613},[603,3211,3213,3215,3217,3219,3221,3224,3226],{"class":605,"line":3212},78,[603,3214,3023],{"class":730},[603,3216,734],{"class":613},[603,3218,2920],{"class":617},[603,3220,883],{"class":613},[603,3222,3223],{"class":969},"toISOString",[603,3225,1203],{"class":730},[603,3227,2139],{"class":613},[603,3229,3231,3233,3235],{"class":605,"line":3230},79,[603,3232,2503],{"class":613},[603,3234,865],{"class":730},[603,3236,655],{"class":613},[603,3238,3240],{"class":605,"line":3239},80,[603,3241,910],{"class":613},[603,3243,3245],{"class":605,"line":3244},81,[603,3246,685],{"emptyLinePlaceholder":684},[603,3248,3250,3252,3254,3257,3259,3261,3263,3265,3267,3269,3272,3274,3276,3278,3280,3282,3284],{"class":605,"line":3249},82,[603,3251,787],{"class":694},[603,3253,931],{"class":694},[603,3255,3256],{"class":730}," me",[603,3258,854],{"class":613},[603,3260,2210],{"class":857},[603,3262,734],{"class":613},[603,3264,1858],{"class":698},[603,3266,943],{"class":613},[603,3268,652],{"class":613},[603,3270,3271],{"class":649},"\u002Fme",[603,3273,652],{"class":613},[603,3275,2226],{"class":613},[603,3277,940],{"class":698},[603,3279,943],{"class":613},[603,3281,2233],{"class":698},[603,3283,949],{"class":613},[603,3285,702],{"class":613},[603,3287,3289,3291,3294,3296,3298,3301,3303,3306,3308,3310],{"class":605,"line":3288},83,[603,3290,957],{"class":694},[603,3292,3293],{"class":617}," userId",[603,3295,740],{"class":613},[603,3297,1588],{"class":613},[603,3299,3300],{"class":617},"authContext",[603,3302,883],{"class":613},[603,3304,3305],{"class":969},"subject",[603,3307,1203],{"class":730},[603,3309,1321],{"class":613},[603,3311,3312],{"class":708}," \u002F\u002F set by JWTAuthMiddleware when a valid token was sent\n",[603,3314,3316],{"class":605,"line":3315},84,[603,3317,685],{"emptyLinePlaceholder":684},[603,3319,3321,3323,3325,3328,3330,3332,3334],{"class":605,"line":3320},85,[603,3322,2284],{"class":609},[603,3324,1644],{"class":730},[603,3326,3327],{"class":617},"userId",[603,3329,999],{"class":613},[603,3331,1579],{"class":613},[603,3333,1141],{"class":730},[603,3335,1668],{"class":613},[603,3337,3339,3341,3343,3345,3347,3349,3351,3353,3355,3357,3359,3361,3364,3366,3368,3370],{"class":605,"line":3338},86,[603,3340,2301],{"class":609},[603,3342,2260],{"class":617},[603,3344,883],{"class":613},[603,3346,2308],{"class":969},[603,3348,854],{"class":730},[603,3350,551],{"class":1496},[603,3352,621],{"class":613},[603,3354,614],{"class":613},[603,3356,2320],{"class":730},[603,3358,734],{"class":613},[603,3360,646],{"class":613},[603,3362,3363],{"class":649},"Authentication is required.",[603,3365,652],{"class":613},[603,3367,640],{"class":613},[603,3369,865],{"class":730},[603,3371,655],{"class":613},[603,3373,3375],{"class":605,"line":3374},87,[603,3376,1699],{"class":613},[603,3378,3380],{"class":605,"line":3379},88,[603,3381,685],{"emptyLinePlaceholder":684},[603,3383,3385,3387,3389,3391,3393,3395,3397,3400,3402,3404,3406],{"class":605,"line":3384},89,[603,3386,957],{"class":694},[603,3388,1650],{"class":617},[603,3390,740],{"class":613},[603,3392,1588],{"class":613},[603,3394,2350],{"class":617},[603,3396,883],{"class":613},[603,3398,3399],{"class":969},"findById",[603,3401,854],{"class":730},[603,3403,3327],{"class":617},[603,3405,865],{"class":730},[603,3407,655],{"class":613},[603,3409,3411],{"class":605,"line":3410},90,[603,3412,685],{"emptyLinePlaceholder":684},[603,3414,3416,3418,3420,3422],{"class":605,"line":3415},91,[603,3417,994],{"class":609},[603,3419,1650],{"class":617},[603,3421,999],{"class":613},[603,3423,1002],{"class":613},[603,3425,3427,3429,3431,3433,3435,3437,3439,3441,3443,3445,3447,3449,3452,3454,3456],{"class":605,"line":3426},92,[603,3428,1008],{"class":613},[603,3430,2260],{"class":617},[603,3432,883],{"class":613},[603,3434,2308],{"class":969},[603,3436,854],{"class":730},[603,3438,551],{"class":1496},[603,3440,621],{"class":613},[603,3442,614],{"class":613},[603,3444,2320],{"class":730},[603,3446,734],{"class":613},[603,3448,646],{"class":613},[603,3450,3451],{"class":649},"The authenticated account no longer exists.",[603,3453,652],{"class":613},[603,3455,640],{"class":613},[603,3457,3458],{"class":730},")\n",[603,3460,3462,3464,3466,3468,3470,3472,3474,3476,3478,3480,3483,3485,3487,3489],{"class":605,"line":3461},93,[603,3463,1024],{"class":613},[603,3465,2260],{"class":617},[603,3467,883],{"class":613},[603,3469,2308],{"class":969},[603,3471,854],{"class":730},[603,3473,559],{"class":1496},[603,3475,621],{"class":613},[603,3477,1975],{"class":617},[603,3479,883],{"class":613},[603,3481,3482],{"class":969},"toProfile",[603,3484,854],{"class":730},[603,3486,1445],{"class":617},[603,3488,1219],{"class":730},[603,3490,655],{"class":613},[603,3492,3494],{"class":605,"line":3493},94,[603,3495,910],{"class":613},[603,3497,3499],{"class":605,"line":3498},95,[603,3500,685],{"emptyLinePlaceholder":684},[603,3502,3504,3506,3508,3511,3513,3516,3518,3521,3523,3525,3527,3529],{"class":605,"line":3503},96,[603,3505,721],{"class":694},[603,3507,724],{"class":694},[603,3509,3510],{"class":730}," readCredentials",[603,3512,854],{"class":613},[603,3514,3515],{"class":857},"body",[603,3517,734],{"class":613},[603,3519,3520],{"class":698}," unknown",[603,3522,1071],{"class":613},[603,3524,1931],{"class":698},[603,3526,1576],{"class":613},[603,3528,1579],{"class":698},[603,3530,702],{"class":613},[603,3532,3534,3536,3538,3541,3544,3547,3549,3552,3554,3557,3559,3561,3564,3566,3568],{"class":605,"line":3533},97,[603,3535,2284],{"class":609},[603,3537,1644],{"class":730},[603,3539,3540],{"class":613},"typeof",[603,3542,3543],{"class":617}," body",[603,3545,3546],{"class":613}," !==",[603,3548,646],{"class":613},[603,3550,3551],{"class":649},"object",[603,3553,652],{"class":613},[603,3555,3556],{"class":613}," ||",[603,3558,3543],{"class":617},[603,3560,999],{"class":613},[603,3562,3563],{"class":613}," null",[603,3565,1141],{"class":730},[603,3567,1690],{"class":609},[603,3569,1706],{"class":613},[603,3571,3573,3575,3577,3579,3581,3583,3585,3587,3589,3592,3595,3597,3599,3601,3603],{"class":605,"line":3572},98,[603,3574,957],{"class":694},[603,3576,614],{"class":613},[603,3578,1301],{"class":617},[603,3580,621],{"class":613},[603,3582,1952],{"class":617},[603,3584,640],{"class":613},[603,3586,740],{"class":613},[603,3588,3543],{"class":617},[603,3590,3591],{"class":609}," as",[603,3593,3594],{"class":698}," Record",[603,3596,943],{"class":613},[603,3598,1419],{"class":698},[603,3600,621],{"class":613},[603,3602,3520],{"class":698},[603,3604,3605],{"class":613},">;\n",[603,3607,3609,3611,3613,3615,3617,3619,3621,3623,3625,3627,3630,3632,3634,3637,3639,3641,3644,3646,3648,3650],{"class":605,"line":3608},99,[603,3610,2284],{"class":609},[603,3612,1644],{"class":730},[603,3614,3540],{"class":613},[603,3616,1301],{"class":617},[603,3618,3546],{"class":613},[603,3620,646],{"class":613},[603,3622,1419],{"class":649},[603,3624,652],{"class":613},[603,3626,3556],{"class":613},[603,3628,3629],{"class":613}," !",[603,3631,1622],{"class":617},[603,3633,883],{"class":613},[603,3635,3636],{"class":969},"includes",[603,3638,854],{"class":730},[603,3640,652],{"class":613},[603,3642,3643],{"class":649},"@",[603,3645,652],{"class":613},[603,3647,2366],{"class":730},[603,3649,1690],{"class":609},[603,3651,1706],{"class":613},[603,3653,3655,3657,3659,3661,3663,3665,3667,3669,3671,3673,3675,3677,3680,3683,3685,3687,3690,3692],{"class":605,"line":3654},100,[603,3656,2284],{"class":609},[603,3658,1644],{"class":730},[603,3660,3540],{"class":613},[603,3662,1952],{"class":617},[603,3664,3546],{"class":613},[603,3666,646],{"class":613},[603,3668,1419],{"class":649},[603,3670,652],{"class":613},[603,3672,3556],{"class":613},[603,3674,1952],{"class":617},[603,3676,883],{"class":613},[603,3678,3679],{"class":617},"length",[603,3681,3682],{"class":613}," \u003C",[603,3684,1975],{"class":617},[603,3686,883],{"class":613},[603,3688,3689],{"class":617},"minimumPasswordLength",[603,3691,1141],{"class":730},[603,3693,1668],{"class":613},[603,3695,3697,3699],{"class":605,"line":3696},101,[603,3698,2301],{"class":609},[603,3700,1706],{"class":613},[603,3702,3704],{"class":605,"line":3703},102,[603,3705,1699],{"class":613},[603,3707,3709,3711,3713,3715,3717,3719],{"class":605,"line":3708},103,[603,3710,994],{"class":609},[603,3712,614],{"class":613},[603,3714,1301],{"class":617},[603,3716,621],{"class":613},[603,3718,1952],{"class":617},[603,3720,1509],{"class":613},[603,3722,3724],{"class":605,"line":3723},104,[603,3725,910],{"class":613},[603,3727,3729],{"class":605,"line":3728},105,[603,3730,685],{"emptyLinePlaceholder":684},[603,3732,3734,3736,3738,3741,3743,3745,3747,3749,3751,3753,3755,3757,3759,3761,3763,3765,3767,3769,3771,3773,3775,3777,3779,3781],{"class":605,"line":3733},106,[603,3735,721],{"class":694},[603,3737,724],{"class":694},[603,3739,3740],{"class":730}," toProfile",[603,3742,854],{"class":613},[603,3744,1445],{"class":857},[603,3746,734],{"class":613},[603,3748,1359],{"class":698},[603,3750,1071],{"class":613},[603,3752,614],{"class":613},[603,3754,1373],{"class":730},[603,3756,734],{"class":613},[603,3758,737],{"class":698},[603,3760,1321],{"class":613},[603,3762,1301],{"class":730},[603,3764,734],{"class":613},[603,3766,737],{"class":698},[603,3768,1321],{"class":613},[603,3770,1331],{"class":730},[603,3772,734],{"class":613},[603,3774,727],{"class":694},[603,3776,737],{"class":698},[603,3778,2093],{"class":617},[603,3780,1144],{"class":613},[603,3782,702],{"class":613},[603,3784,3786,3788,3790,3792,3794,3796,3798,3800,3802,3804,3806,3808,3810,3812,3814,3816,3818,3820,3822,3824],{"class":605,"line":3785},107,[603,3787,994],{"class":609},[603,3789,614],{"class":613},[603,3791,1373],{"class":730},[603,3793,734],{"class":613},[603,3795,1650],{"class":617},[603,3797,883],{"class":613},[603,3799,1530],{"class":617},[603,3801,621],{"class":613},[603,3803,1301],{"class":730},[603,3805,734],{"class":613},[603,3807,1650],{"class":617},[603,3809,883],{"class":613},[603,3811,1622],{"class":617},[603,3813,621],{"class":613},[603,3815,1331],{"class":730},[603,3817,734],{"class":613},[603,3819,1650],{"class":617},[603,3821,883],{"class":613},[603,3823,3012],{"class":617},[603,3825,1509],{"class":613},[603,3827,3829],{"class":605,"line":3828},108,[603,3830,910],{"class":613},[603,3832,3834],{"class":605,"line":3833},109,[603,3835,1232],{"class":613},[499,3837,3838,3840,3841,3844,3845,3848,3849,3852,3853,3855,3856,3858,3859,3861,3862,3864],{},[509,3839,538],{}," lets an ",[518,3842,3843],{},"anonymous"," request through (storing a ",[509,3846,3847],{},"null"," principal) and only\nhard-rejects a present-but-",[573,3850,3851],{},"invalid"," token with ",[509,3854,551],{},". That's deliberate: one middleware can sit on a\ncontroller mixing public (",[509,3857,2221],{},", ",[509,3860,2595],{},") and protected (",[509,3863,3271],{},") routes — the handlers decide who\nneeds a principal.",[578,3866,3868],{"id":3867},"_4-wire-the-auth-module","4. Wire the auth module",[499,3870,3871,3872,3875,3876,3878,3879,3881,3882,3885,3886,3888,3889,3892,3893,3896,3897,3899,3900,1249,3903,3905,3906,3909,3910,3913,3914,3917,3918,3920,3921,3923],{},"The compiler recovers a class's constructor — your app's from source, a ",[573,3873,3874],{},"package"," class's from the types it\nships. ",[509,3877,538],{},"'s constructor is only DI tokens (an ",[509,3880,529],{},", an optional flag), so it's a\n",[518,3883,3884],{},"bare provider"," — no wrapper class needed. ",[509,3887,570],{}," is reserved for construction DI can't express:\n",[509,3890,3891],{},"JWTFactory"," reads a ",[518,3894,3895],{},"property"," of the injected key pair, ",[509,3898,946],{}," resolves it through an async\n",[518,3901,3902],{},"static factory",[509,3904,529],{}," takes a non-DI constructor argument (a ",[509,3907,3908],{},"CookieTokenSource","). There is no\nseparate ",[509,3911,3912],{},"JWTVerifier"," provider at all: because ",[509,3915,3916],{},"JWTFactory extends JWTVerifier"," (it verifies with the same\nkey it signs with), providing ",[509,3919,3891],{}," alone satisfies every ",[509,3922,3912],{},"-typed injection and export.",[593,3925,3928],{"className":595,"code":3926,"filename":3927,"language":598,"meta":599,"style":599},"import {\n  AuthContext,\n  CookieTokenSource,\n  JWTAuthMiddleware,\n  JWTFactory,\n  JWTVerifier,\n  PasswordHashingAlgorithm,\n  Scrypt,\n} from \"@heximon\u002Fauth\";\nimport { Context, Module } from \"@heximon\u002Fruntime\";\nimport { AuthController } from \".\u002Fauth.controller\";\nimport { IdentityKeyPair } from \".\u002Fidentity-key-pair\";\nimport { UserAccountStore } from \".\u002Fuser-account-store\";\n\nexport class AuthModule extends Module({\n  providers: [\n    UserAccountStore,\n    { provide: PasswordHashingAlgorithm, useClass: Scrypt }, \u002F\u002F a port, so an edge app swaps Pbkdf2 with no code change\n    JWTAuthMiddleware, \u002F\u002F bare: ctor is plain DI tokens (AuthContext + a default flag)\n    { provide: IdentityKeyPair, useFactory: () => IdentityKeyPair.resolve() }, \u002F\u002F async, awaited at boot\n    { provide: JWTFactory, useFactory: (keys: IdentityKeyPair) => new JWTFactory(keys.privateKey) },\n    {\n      provide: AuthContext,\n      useFactory: (context: Context, verifier: JWTVerifier) =>\n        new AuthContext(context, verifier, new CookieTokenSource(\"accessToken\")),\n    },\n  ],\n  http: { controllers: [AuthController] },\n  \u002F\u002F Re-export so other feature modules authenticate with the SAME middleware and read the SAME principal.\n  exports: [AuthContext, JWTAuthMiddleware, JWTVerifier, JWTFactory, UserAccountStore, PasswordHashingAlgorithm],\n}) {}\n","src\u002Fauth\u002Fauth.module.ts",[509,3929,3930,3936,3943,3950,3957,3964,3971,3978,3985,3999,4025,4046,4067,4087,4091,4108,4118,4125,4153,4163,4200,4243,4248,4259,4289,4322,4327,4334,4353,4358,4391],{"__ignoreMap":599},[603,3931,3932,3934],{"class":605,"line":606},[603,3933,610],{"class":609},[603,3935,702],{"class":613},[603,3937,3938,3941],{"class":605,"line":658},[603,3939,3940],{"class":617},"  AuthContext",[603,3942,2139],{"class":613},[603,3944,3945,3948],{"class":605,"line":681},[603,3946,3947],{"class":617},"  CookieTokenSource",[603,3949,2139],{"class":613},[603,3951,3952,3955],{"class":605,"line":688},[603,3953,3954],{"class":617},"  JWTAuthMiddleware",[603,3956,2139],{"class":613},[603,3958,3959,3962],{"class":605,"line":705},[603,3960,3961],{"class":617},"  JWTFactory",[603,3963,2139],{"class":613},[603,3965,3966,3969],{"class":605,"line":712},[603,3967,3968],{"class":617},"  JWTVerifier",[603,3970,2139],{"class":613},[603,3972,3973,3976],{"class":605,"line":718},[603,3974,3975],{"class":617},"  PasswordHashingAlgorithm",[603,3977,2139],{"class":613},[603,3979,3980,3983],{"class":605,"line":752},[603,3981,3982],{"class":617},"  Scrypt",[603,3984,2139],{"class":613},[603,3986,3987,3989,3991,3993,3995,3997],{"class":605,"line":779},[603,3988,1144],{"class":613},[603,3990,643],{"class":609},[603,3992,646],{"class":613},[603,3994,566],{"class":649},[603,3996,652],{"class":613},[603,3998,655],{"class":613},[603,4000,4001,4003,4005,4008,4010,4013,4015,4017,4019,4021,4023],{"class":605,"line":784},[603,4002,610],{"class":609},[603,4004,614],{"class":613},[603,4006,4007],{"class":617}," Context",[603,4009,621],{"class":613},[603,4011,4012],{"class":617}," Module",[603,4014,640],{"class":613},[603,4016,643],{"class":609},[603,4018,646],{"class":613},[603,4020,674],{"class":649},[603,4022,652],{"class":613},[603,4024,655],{"class":613},[603,4026,4027,4029,4031,4033,4035,4037,4039,4042,4044],{"class":605,"line":814},[603,4028,610],{"class":609},[603,4030,614],{"class":613},[603,4032,1975],{"class":617},[603,4034,640],{"class":613},[603,4036,643],{"class":609},[603,4038,646],{"class":613},[603,4040,4041],{"class":649},".\u002Fauth.controller",[603,4043,652],{"class":613},[603,4045,655],{"class":613},[603,4047,4048,4050,4052,4054,4056,4058,4060,4063,4065],{"class":605,"line":841},[603,4049,610],{"class":609},[603,4051,614],{"class":613},[603,4053,699],{"class":617},[603,4055,640],{"class":613},[603,4057,643],{"class":609},[603,4059,646],{"class":613},[603,4061,4062],{"class":649},".\u002Fidentity-key-pair",[603,4064,652],{"class":613},[603,4066,655],{"class":613},[603,4068,4069,4071,4073,4075,4077,4079,4081,4083,4085],{"class":605,"line":846},[603,4070,610],{"class":609},[603,4072,614],{"class":613},[603,4074,1396],{"class":617},[603,4076,640],{"class":613},[603,4078,643],{"class":609},[603,4080,646],{"class":613},[603,4082,1895],{"class":649},[603,4084,652],{"class":613},[603,4086,655],{"class":613},[603,4088,4089],{"class":605,"line":870},[603,4090,685],{"emptyLinePlaceholder":684},[603,4092,4093,4095,4097,4100,4102,4104,4106],{"class":605,"line":890},[603,4094,691],{"class":609},[603,4096,695],{"class":694},[603,4098,4099],{"class":698}," AuthModule",[603,4101,1362],{"class":694},[603,4103,4012],{"class":969},[603,4105,854],{"class":617},[603,4107,1668],{"class":613},[603,4109,4110,4113,4115],{"class":605,"line":907},[603,4111,4112],{"class":730},"  providers",[603,4114,734],{"class":613},[603,4116,4117],{"class":617}," [\n",[603,4119,4120,4123],{"class":605,"line":913},[603,4121,4122],{"class":617},"    UserAccountStore",[603,4124,2139],{"class":613},[603,4126,4127,4130,4133,4135,4137,4139,4142,4144,4147,4150],{"class":605,"line":918},[603,4128,4129],{"class":613},"    {",[603,4131,4132],{"class":730}," provide",[603,4134,734],{"class":613},[603,4136,1830],{"class":617},[603,4138,621],{"class":613},[603,4140,4141],{"class":730}," useClass",[603,4143,734],{"class":613},[603,4145,4146],{"class":617}," Scrypt ",[603,4148,4149],{"class":613},"},",[603,4151,4152],{"class":708}," \u002F\u002F a port, so an edge app swaps Pbkdf2 with no code change\n",[603,4154,4155,4158,4160],{"class":605,"line":924},[603,4156,4157],{"class":617},"    JWTAuthMiddleware",[603,4159,621],{"class":613},[603,4161,4162],{"class":708}," \u002F\u002F bare: ctor is plain DI tokens (AuthContext + a default flag)\n",[603,4164,4165,4167,4169,4171,4173,4175,4178,4180,4183,4186,4188,4190,4193,4195,4197],{"class":605,"line":954},[603,4166,4129],{"class":613},[603,4168,4132],{"class":730},[603,4170,734],{"class":613},[603,4172,699],{"class":617},[603,4174,621],{"class":613},[603,4176,4177],{"class":969}," useFactory",[603,4179,734],{"class":613},[603,4181,4182],{"class":613}," ()",[603,4184,4185],{"class":694}," =>",[603,4187,699],{"class":617},[603,4189,883],{"class":613},[603,4191,4192],{"class":969},"resolve",[603,4194,2940],{"class":617},[603,4196,4149],{"class":613},[603,4198,4199],{"class":708}," \u002F\u002F async, awaited at boot\n",[603,4201,4202,4204,4206,4208,4210,4212,4214,4216,4218,4221,4223,4225,4227,4229,4231,4233,4236,4238,4241],{"class":605,"line":986},[603,4203,4129],{"class":613},[603,4205,4132],{"class":730},[603,4207,734],{"class":613},[603,4209,1825],{"class":617},[603,4211,621],{"class":613},[603,4213,4177],{"class":969},[603,4215,734],{"class":613},[603,4217,1644],{"class":613},[603,4219,4220],{"class":857},"keys",[603,4222,734],{"class":613},[603,4224,699],{"class":698},[603,4226,865],{"class":613},[603,4228,4185],{"class":694},[603,4230,1112],{"class":613},[603,4232,1825],{"class":969},[603,4234,4235],{"class":617},"(keys",[603,4237,883],{"class":613},[603,4239,4240],{"class":617},"privateKey) ",[603,4242,3017],{"class":613},[603,4244,4245],{"class":605,"line":991},[603,4246,4247],{"class":613},"    {\n",[603,4249,4250,4253,4255,4257],{"class":605,"line":1005},[603,4251,4252],{"class":730},"      provide",[603,4254,734],{"class":613},[603,4256,1815],{"class":617},[603,4258,2139],{"class":613},[603,4260,4261,4264,4266,4268,4271,4273,4275,4277,4280,4282,4284,4286],{"class":605,"line":1021},[603,4262,4263],{"class":969},"      useFactory",[603,4265,734],{"class":613},[603,4267,1644],{"class":613},[603,4269,4270],{"class":857},"context",[603,4272,734],{"class":613},[603,4274,4007],{"class":698},[603,4276,621],{"class":613},[603,4278,4279],{"class":857}," verifier",[603,4281,734],{"class":613},[603,4283,637],{"class":698},[603,4285,865],{"class":613},[603,4287,4288],{"class":694}," =>\n",[603,4290,4291,4294,4296,4299,4301,4303,4305,4307,4310,4312,4314,4316,4318,4320],{"class":605,"line":1043},[603,4292,4293],{"class":613},"        new",[603,4295,1815],{"class":969},[603,4297,4298],{"class":617},"(context",[603,4300,621],{"class":613},[603,4302,4279],{"class":617},[603,4304,621],{"class":613},[603,4306,1112],{"class":613},[603,4308,4309],{"class":969}," CookieTokenSource",[603,4311,854],{"class":617},[603,4313,652],{"class":613},[603,4315,3073],{"class":649},[603,4317,652],{"class":613},[603,4319,1219],{"class":617},[603,4321,2139],{"class":613},[603,4323,4324],{"class":605,"line":1048},[603,4325,4326],{"class":613},"    },\n",[603,4328,4329,4332],{"class":605,"line":1053},[603,4330,4331],{"class":617},"  ]",[603,4333,2139],{"class":613},[603,4335,4336,4339,4341,4343,4346,4348,4351],{"class":605,"line":1078},[603,4337,4338],{"class":730},"  http",[603,4340,734],{"class":613},[603,4342,614],{"class":613},[603,4344,4345],{"class":730}," controllers",[603,4347,734],{"class":613},[603,4349,4350],{"class":617}," [AuthController] ",[603,4352,3017],{"class":613},[603,4354,4355],{"class":605,"line":1102},[603,4356,4357],{"class":708},"  \u002F\u002F Re-export so other feature modules authenticate with the SAME middleware and read the SAME principal.\n",[603,4359,4360,4363,4365,4368,4370,4372,4374,4376,4378,4380,4382,4384,4386,4389],{"class":605,"line":1107},[603,4361,4362],{"class":730},"  exports",[603,4364,734],{"class":613},[603,4366,4367],{"class":617}," [AuthContext",[603,4369,621],{"class":613},[603,4371,1820],{"class":617},[603,4373,621],{"class":613},[603,4375,637],{"class":617},[603,4377,621],{"class":613},[603,4379,1825],{"class":617},[603,4381,621],{"class":613},[603,4383,1396],{"class":617},[603,4385,621],{"class":613},[603,4387,4388],{"class":617}," PasswordHashingAlgorithm]",[603,4390,2139],{"class":613},[603,4392,4393,4395,4397],{"class":605,"line":1151},[603,4394,1144],{"class":613},[603,4396,1141],{"class":617},[603,4398,4399],{"class":613},"{}\n",[499,4401,4402,4404,4405,4407,4408,4410,4411,4413,4414,4417],{},[509,4403,529],{},"'s factory still asks for a ",[509,4406,3912],{}," (the capability it actually needs to verify), and the\ncompiler resolves that parameter to the ",[509,4409,3891],{}," instance above — the ",[509,4412,3912],{}," import is for that\ntype annotation, and for the ",[509,4415,4416],{},"exports"," list, not for a provider of its own.",[578,4419,4421],{"id":4420},"_5-guard-an-endpoint-by-scope","5. Guard an endpoint by scope",[499,4423,4424,4425,588,4428,4431,4432,4434],{},"Now the payoff: a route only an admin may reach. The admin module ",[518,4426,4427],{},"imports",[509,4429,4430],{},"AuthModule"," and reuses its\nexports — same middleware authenticating the request, same ",[509,4433,529],{}," carrying the principal.",[593,4436,4439],{"className":595,"code":4437,"filename":4438,"language":598,"meta":599,"style":599},"import { AuthContext, JWTAuthMiddleware } from \"@heximon\u002Fauth\";\nimport type { Controller, Get } from \"@heximon\u002Fhttp\";\n\nexport class AdminController implements Controller\u003C{ prefix: \"\u002Fadmin\"; middlewares: [JWTAuthMiddleware] }> {\n  private static readonly requiredScope: string = \"admin:stats:read\";\n\n  public constructor(private readonly authContext: AuthContext) {}\n\n  public async stats(action: Get\u003C\"\u002Fstats\">): Promise\u003CResponse> {\n    if (this.authContext.principal() === null) {\n      return action.respond(401, { error: \"Authentication is required.\" });\n    }\n    if (!this.authContext.isAllowed(AdminController.requiredScope)) {\n      return action.respond(403, { error: `Requires the '${AdminController.requiredScope}' scope.` });\n    }\n\n    return action.respond(200, { totalRequests: 42, activeSessions: 7 });\n  }\n}\n","src\u002Fadmin\u002Fadmin.controller.ts",[509,4440,4441,4465,4491,4495,4537,4563,4567,4590,4594,4632,4658,4692,4696,4726,4777,4781,4785,4827,4831],{"__ignoreMap":599},[603,4442,4443,4445,4447,4449,4451,4453,4455,4457,4459,4461,4463],{"class":605,"line":606},[603,4444,610],{"class":609},[603,4446,614],{"class":613},[603,4448,1815],{"class":617},[603,4450,621],{"class":613},[603,4452,1820],{"class":617},[603,4454,640],{"class":613},[603,4456,643],{"class":609},[603,4458,646],{"class":613},[603,4460,566],{"class":649},[603,4462,652],{"class":613},[603,4464,655],{"class":613},[603,4466,4467,4469,4471,4473,4475,4477,4479,4481,4483,4485,4487,4489],{"class":605,"line":658},[603,4468,610],{"class":609},[603,4470,624],{"class":609},[603,4472,614],{"class":613},[603,4474,1853],{"class":617},[603,4476,621],{"class":613},[603,4478,1858],{"class":617},[603,4480,640],{"class":613},[603,4482,643],{"class":609},[603,4484,646],{"class":613},[603,4486,1872],{"class":649},[603,4488,652],{"class":613},[603,4490,655],{"class":613},[603,4492,4493],{"class":605,"line":681},[603,4494,685],{"emptyLinePlaceholder":684},[603,4496,4497,4499,4501,4504,4506,4508,4510,4512,4514,4516,4519,4521,4523,4525,4527,4529,4531,4533,4535],{"class":605,"line":688},[603,4498,691],{"class":609},[603,4500,695],{"class":694},[603,4502,4503],{"class":698}," AdminController",[603,4505,1978],{"class":694},[603,4507,1853],{"class":698},[603,4509,1983],{"class":613},[603,4511,1986],{"class":730},[603,4513,734],{"class":613},[603,4515,646],{"class":613},[603,4517,4518],{"class":649},"\u002Fadmin",[603,4520,652],{"class":613},[603,4522,1321],{"class":613},[603,4524,2000],{"class":730},[603,4526,734],{"class":613},[603,4528,2005],{"class":617},[603,4530,538],{"class":698},[603,4532,2010],{"class":617},[603,4534,2013],{"class":613},[603,4536,702],{"class":613},[603,4538,4539,4541,4543,4545,4548,4550,4552,4554,4556,4559,4561],{"class":605,"line":705},[603,4540,721],{"class":694},[603,4542,724],{"class":694},[603,4544,727],{"class":694},[603,4546,4547],{"class":730}," requiredScope",[603,4549,734],{"class":613},[603,4551,737],{"class":698},[603,4553,740],{"class":613},[603,4555,646],{"class":613},[603,4557,4558],{"class":649},"admin:stats:read",[603,4560,652],{"class":613},[603,4562,655],{"class":613},[603,4564,4565],{"class":605,"line":712},[603,4566,685],{"emptyLinePlaceholder":684},[603,4568,4569,4571,4573,4575,4578,4580,4582,4584,4586,4588],{"class":605,"line":718},[603,4570,787],{"class":694},[603,4572,851],{"class":694},[603,4574,854],{"class":613},[603,4576,4577],{"class":694},"private",[603,4579,727],{"class":694},[603,4581,2178],{"class":857},[603,4583,734],{"class":613},[603,4585,1815],{"class":698},[603,4587,865],{"class":613},[603,4589,2192],{"class":613},[603,4591,4592],{"class":605,"line":752},[603,4593,685],{"emptyLinePlaceholder":684},[603,4595,4596,4598,4600,4603,4605,4607,4609,4611,4613,4615,4618,4620,4622,4624,4626,4628,4630],{"class":605,"line":779},[603,4597,787],{"class":694},[603,4599,931],{"class":694},[603,4601,4602],{"class":730}," stats",[603,4604,854],{"class":613},[603,4606,2210],{"class":857},[603,4608,734],{"class":613},[603,4610,1858],{"class":698},[603,4612,943],{"class":613},[603,4614,652],{"class":613},[603,4616,4617],{"class":649},"\u002Fstats",[603,4619,652],{"class":613},[603,4621,2226],{"class":613},[603,4623,940],{"class":698},[603,4625,943],{"class":613},[603,4627,2233],{"class":698},[603,4629,949],{"class":613},[603,4631,702],{"class":613},[603,4633,4634,4636,4638,4640,4642,4644,4647,4649,4652,4654,4656],{"class":605,"line":784},[603,4635,2284],{"class":609},[603,4637,1644],{"class":730},[603,4639,1482],{"class":613},[603,4641,3300],{"class":617},[603,4643,883],{"class":613},[603,4645,4646],{"class":969},"principal",[603,4648,2940],{"class":730},[603,4650,4651],{"class":613},"===",[603,4653,3563],{"class":613},[603,4655,1141],{"class":730},[603,4657,1668],{"class":613},[603,4659,4660,4662,4664,4666,4668,4670,4672,4674,4676,4678,4680,4682,4684,4686,4688,4690],{"class":605,"line":814},[603,4661,2301],{"class":609},[603,4663,2260],{"class":617},[603,4665,883],{"class":613},[603,4667,2308],{"class":969},[603,4669,854],{"class":730},[603,4671,551],{"class":1496},[603,4673,621],{"class":613},[603,4675,614],{"class":613},[603,4677,2320],{"class":730},[603,4679,734],{"class":613},[603,4681,646],{"class":613},[603,4683,3363],{"class":649},[603,4685,652],{"class":613},[603,4687,640],{"class":613},[603,4689,865],{"class":730},[603,4691,655],{"class":613},[603,4693,4694],{"class":605,"line":841},[603,4695,1699],{"class":613},[603,4697,4698,4700,4702,4705,4707,4709,4712,4714,4717,4719,4722,4724],{"class":605,"line":846},[603,4699,2284],{"class":609},[603,4701,1644],{"class":730},[603,4703,4704],{"class":613},"!this.",[603,4706,3300],{"class":617},[603,4708,883],{"class":613},[603,4710,4711],{"class":969},"isAllowed",[603,4713,854],{"class":730},[603,4715,4716],{"class":617},"AdminController",[603,4718,883],{"class":613},[603,4720,4721],{"class":617},"requiredScope",[603,4723,2366],{"class":730},[603,4725,1668],{"class":613},[603,4727,4728,4730,4732,4734,4736,4738,4740,4742,4744,4746,4748,4751,4754,4757,4759,4761,4763,4765,4768,4771,4773,4775],{"class":605,"line":870},[603,4729,2301],{"class":609},[603,4731,2260],{"class":617},[603,4733,883],{"class":613},[603,4735,2308],{"class":969},[603,4737,854],{"class":730},[603,4739,555],{"class":1496},[603,4741,621],{"class":613},[603,4743,614],{"class":613},[603,4745,2320],{"class":730},[603,4747,734],{"class":613},[603,4749,4750],{"class":613}," `",[603,4752,4753],{"class":649},"Requires the '",[603,4755,4756],{"class":613},"${",[603,4758,4716],{"class":617},[603,4760,883],{"class":613},[603,4762,4721],{"class":617},[603,4764,1144],{"class":613},[603,4766,4767],{"class":649},"' scope.",[603,4769,4770],{"class":613},"`",[603,4772,640],{"class":613},[603,4774,865],{"class":730},[603,4776,655],{"class":613},[603,4778,4779],{"class":605,"line":890},[603,4780,1699],{"class":613},[603,4782,4783],{"class":605,"line":907},[603,4784,685],{"emptyLinePlaceholder":684},[603,4786,4787,4789,4791,4793,4795,4797,4799,4801,4803,4806,4808,4811,4813,4816,4818,4821,4823,4825],{"class":605,"line":913},[603,4788,994],{"class":609},[603,4790,2260],{"class":617},[603,4792,883],{"class":613},[603,4794,2308],{"class":969},[603,4796,854],{"class":730},[603,4798,559],{"class":1496},[603,4800,621],{"class":613},[603,4802,614],{"class":613},[603,4804,4805],{"class":730}," totalRequests",[603,4807,734],{"class":613},[603,4809,4810],{"class":1496}," 42",[603,4812,621],{"class":613},[603,4814,4815],{"class":730}," activeSessions",[603,4817,734],{"class":613},[603,4819,4820],{"class":1496}," 7",[603,4822,640],{"class":613},[603,4824,865],{"class":730},[603,4826,655],{"class":613},[603,4828,4829],{"class":605,"line":918},[603,4830,910],{"class":613},[603,4832,4833],{"class":605,"line":924},[603,4834,1232],{"class":613},[593,4836,4839],{"className":595,"code":4837,"filename":4838,"language":598,"meta":599,"style":599},"import { Module } from \"@heximon\u002Fruntime\";\nimport { AuthModule } from \"..\u002Fauth\u002Fauth.module\";\nimport { AdminController } from \".\u002Fadmin.controller\";\n\nexport class AdminModule extends Module({\n  imports: [AuthModule],\n  http: { controllers: [AdminController] },\n}) {}\n","src\u002Fadmin\u002Fadmin.module.ts",[509,4840,4841,4861,4882,4903,4907,4924,4936,4953],{"__ignoreMap":599},[603,4842,4843,4845,4847,4849,4851,4853,4855,4857,4859],{"class":605,"line":606},[603,4844,610],{"class":609},[603,4846,614],{"class":613},[603,4848,4012],{"class":617},[603,4850,640],{"class":613},[603,4852,643],{"class":609},[603,4854,646],{"class":613},[603,4856,674],{"class":649},[603,4858,652],{"class":613},[603,4860,655],{"class":613},[603,4862,4863,4865,4867,4869,4871,4873,4875,4878,4880],{"class":605,"line":658},[603,4864,610],{"class":609},[603,4866,614],{"class":613},[603,4868,4099],{"class":617},[603,4870,640],{"class":613},[603,4872,643],{"class":609},[603,4874,646],{"class":613},[603,4876,4877],{"class":649},"..\u002Fauth\u002Fauth.module",[603,4879,652],{"class":613},[603,4881,655],{"class":613},[603,4883,4884,4886,4888,4890,4892,4894,4896,4899,4901],{"class":605,"line":681},[603,4885,610],{"class":609},[603,4887,614],{"class":613},[603,4889,4503],{"class":617},[603,4891,640],{"class":613},[603,4893,643],{"class":609},[603,4895,646],{"class":613},[603,4897,4898],{"class":649},".\u002Fadmin.controller",[603,4900,652],{"class":613},[603,4902,655],{"class":613},[603,4904,4905],{"class":605,"line":688},[603,4906,685],{"emptyLinePlaceholder":684},[603,4908,4909,4911,4913,4916,4918,4920,4922],{"class":605,"line":705},[603,4910,691],{"class":609},[603,4912,695],{"class":694},[603,4914,4915],{"class":698}," AdminModule",[603,4917,1362],{"class":694},[603,4919,4012],{"class":969},[603,4921,854],{"class":617},[603,4923,1668],{"class":613},[603,4925,4926,4929,4931,4934],{"class":605,"line":712},[603,4927,4928],{"class":730},"  imports",[603,4930,734],{"class":613},[603,4932,4933],{"class":617}," [AuthModule]",[603,4935,2139],{"class":613},[603,4937,4938,4940,4942,4944,4946,4948,4951],{"class":605,"line":718},[603,4939,4338],{"class":730},[603,4941,734],{"class":613},[603,4943,614],{"class":613},[603,4945,4345],{"class":730},[603,4947,734],{"class":613},[603,4949,4950],{"class":617}," [AdminController] ",[603,4952,3017],{"class":613},[603,4954,4955,4957,4959],{"class":605,"line":752},[603,4956,1144],{"class":613},[603,4958,1141],{"class":617},[603,4960,4399],{"class":613},[499,4962,4963,4964,4967,4968,4971,4972,4974,4975,4977],{},"This handler checks the scope itself because the route is declared inline — by the ",[509,4965,4966],{},"Get\u003C\"\u002Fstats\">","\nparameter type. Inline routes carry no scope metadata, so an explicit ",[509,4969,4970],{},"authContext.isAllowed(...)"," is the\nright tool, returning the same ",[509,4973,551],{},"\u002F",[509,4976,555],{}," distinction a guard would.",[499,4979,4980,4981,4984,4985,4988,4989,4992,4993,4996,4997,588,5000,5002],{},"For a ",[518,4982,4983],{},"contract"," route, declare the scope with ",[509,4986,4987],{},"route.scopes(...)"," and let the shipped ",[509,4990,4991],{},"GuardMiddleware","\nenforce it — no per-handler check. List it in the ",[509,4994,4995],{},"middlewares"," array ",[518,4998,4999],{},"after",[509,5001,538],{},", since\nthe guard reads the principal the auth middleware stores.",[593,5004,5007],{"className":595,"code":5005,"filename":5006,"language":598,"meta":599,"style":599},"import { Contract, Route } from \"@heximon\u002Fcontract\";\nimport { CreateTaskBody, TaskResponse } from \".\u002Ftask.dto\";\n\nexport class TaskApi extends Contract({\n  prefix: \"\u002Fapi\u002Ftasks\",\n  routes: {\n    list: Route.get(\"\u002F\").scopes(\"task:read\").responses({ 200: TaskResponse }),\n    create: Route.post(\"\u002F\").scopes(\"task:create\").body(CreateTaskBody).responses({ 201: TaskResponse }),\n  },\n}) {}\n","src\u002Ftasks\u002Ftask.api.ts",[509,5008,5009,5036,5063,5067,5084,5100,5109,5171,5238,5243],{"__ignoreMap":599},[603,5010,5011,5013,5015,5018,5020,5023,5025,5027,5029,5032,5034],{"class":605,"line":606},[603,5012,610],{"class":609},[603,5014,614],{"class":613},[603,5016,5017],{"class":617}," Contract",[603,5019,621],{"class":613},[603,5021,5022],{"class":617}," Route",[603,5024,640],{"class":613},[603,5026,643],{"class":609},[603,5028,646],{"class":613},[603,5030,5031],{"class":649},"@heximon\u002Fcontract",[603,5033,652],{"class":613},[603,5035,655],{"class":613},[603,5037,5038,5040,5042,5045,5047,5050,5052,5054,5056,5059,5061],{"class":605,"line":658},[603,5039,610],{"class":609},[603,5041,614],{"class":613},[603,5043,5044],{"class":617}," CreateTaskBody",[603,5046,621],{"class":613},[603,5048,5049],{"class":617}," TaskResponse",[603,5051,640],{"class":613},[603,5053,643],{"class":609},[603,5055,646],{"class":613},[603,5057,5058],{"class":649},".\u002Ftask.dto",[603,5060,652],{"class":613},[603,5062,655],{"class":613},[603,5064,5065],{"class":605,"line":681},[603,5066,685],{"emptyLinePlaceholder":684},[603,5068,5069,5071,5073,5076,5078,5080,5082],{"class":605,"line":688},[603,5070,691],{"class":609},[603,5072,695],{"class":694},[603,5074,5075],{"class":698}," TaskApi",[603,5077,1362],{"class":694},[603,5079,5017],{"class":969},[603,5081,854],{"class":617},[603,5083,1668],{"class":613},[603,5085,5086,5089,5091,5093,5096,5098],{"class":605,"line":705},[603,5087,5088],{"class":730},"  prefix",[603,5090,734],{"class":613},[603,5092,646],{"class":613},[603,5094,5095],{"class":649},"\u002Fapi\u002Ftasks",[603,5097,652],{"class":613},[603,5099,2139],{"class":613},[603,5101,5102,5105,5107],{"class":605,"line":712},[603,5103,5104],{"class":730},"  routes",[603,5106,734],{"class":613},[603,5108,702],{"class":613},[603,5110,5111,5114,5116,5118,5120,5122,5124,5126,5128,5130,5132,5134,5137,5139,5141,5144,5146,5148,5150,5153,5155,5157,5160,5162,5165,5167,5169],{"class":605,"line":718},[603,5112,5113],{"class":730},"    list",[603,5115,734],{"class":613},[603,5117,5022],{"class":617},[603,5119,883],{"class":613},[603,5121,970],{"class":969},[603,5123,854],{"class":617},[603,5125,652],{"class":613},[603,5127,4974],{"class":649},[603,5129,652],{"class":613},[603,5131,865],{"class":617},[603,5133,883],{"class":613},[603,5135,5136],{"class":969},"scopes",[603,5138,854],{"class":617},[603,5140,652],{"class":613},[603,5142,5143],{"class":649},"task:read",[603,5145,652],{"class":613},[603,5147,865],{"class":617},[603,5149,883],{"class":613},[603,5151,5152],{"class":969},"responses",[603,5154,854],{"class":617},[603,5156,1119],{"class":613},[603,5158,5159],{"class":1496}," 200",[603,5161,734],{"class":613},[603,5163,5164],{"class":617}," TaskResponse ",[603,5166,1144],{"class":613},[603,5168,865],{"class":617},[603,5170,2139],{"class":613},[603,5172,5173,5176,5178,5180,5182,5185,5187,5189,5191,5193,5195,5197,5199,5201,5203,5206,5208,5210,5212,5214,5217,5219,5221,5223,5225,5228,5230,5232,5234,5236],{"class":605,"line":752},[603,5174,5175],{"class":730},"    create",[603,5177,734],{"class":613},[603,5179,5022],{"class":617},[603,5181,883],{"class":613},[603,5183,5184],{"class":969},"post",[603,5186,854],{"class":617},[603,5188,652],{"class":613},[603,5190,4974],{"class":649},[603,5192,652],{"class":613},[603,5194,865],{"class":617},[603,5196,883],{"class":613},[603,5198,5136],{"class":969},[603,5200,854],{"class":617},[603,5202,652],{"class":613},[603,5204,5205],{"class":649},"task:create",[603,5207,652],{"class":613},[603,5209,865],{"class":617},[603,5211,883],{"class":613},[603,5213,3515],{"class":969},[603,5215,5216],{"class":617},"(CreateTaskBody)",[603,5218,883],{"class":613},[603,5220,5152],{"class":969},[603,5222,854],{"class":617},[603,5224,1119],{"class":613},[603,5226,5227],{"class":1496}," 201",[603,5229,734],{"class":613},[603,5231,5164],{"class":617},[603,5233,1144],{"class":613},[603,5235,865],{"class":617},[603,5237,2139],{"class":613},[603,5239,5240],{"class":605,"line":779},[603,5241,5242],{"class":613},"  },\n",[603,5244,5245,5247,5249],{"class":605,"line":784},[603,5246,1144],{"class":613},[603,5248,1141],{"class":617},[603,5250,4399],{"class":613},[578,5252,5254],{"id":5253},"_6-run-it","6. Run it",[499,5256,5257,5258,5261],{},"There's no entry file to write — ",[509,5259,5260],{},"pnpm dev"," serves the app. Register, log in, capture the token, then call\nthe guarded route with it.",[593,5263,5268],{"className":5264,"code":5265,"filename":5266,"language":5267,"meta":599,"style":599},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# Register and log in -> capture the token from the JSON body\ncurl -s -X POST localhost:3000\u002Fauth\u002Fregister \\\n  -H 'content-type: application\u002Fjson' \\\n  -d '{\"email\":\"alice@example.com\",\"password\":\"correct-horse-battery\"}'\n\nTOKEN=$(curl -s -X POST localhost:3000\u002Fauth\u002Flogin \\\n  -H 'content-type: application\u002Fjson' \\\n  -d '{\"email\":\"alice@example.com\",\"password\":\"correct-horse-battery\"}' | jq -r .accessToken)\n\n# Call a protected route with the bearer token\ncurl -s localhost:3000\u002Fauth\u002Fme -H \"authorization: Bearer $TOKEN\"\n# -> 200 with the caller's profile\n\ncurl -s localhost:3000\u002Fadmin\u002Fstats -H \"authorization: Bearer $TOKEN\"\n# -> 403: alice holds profile:read, not admin:stats:read\n","terminal","bash",[509,5269,5270,5275,5295,5311,5324,5328,5349,5361,5384,5388,5393,5416,5421,5425,5444],{"__ignoreMap":599},[603,5271,5272],{"class":605,"line":606},[603,5273,5274],{"class":708},"# Register and log in -> capture the token from the JSON body\n",[603,5276,5277,5280,5283,5286,5289,5292],{"class":605,"line":658},[603,5278,5279],{"class":698},"curl",[603,5281,5282],{"class":649}," -s",[603,5284,5285],{"class":649}," -X",[603,5287,5288],{"class":649}," POST",[603,5290,5291],{"class":649}," localhost:3000\u002Fauth\u002Fregister",[603,5293,5294],{"class":617}," \\\n",[603,5296,5297,5300,5303,5306,5309],{"class":605,"line":681},[603,5298,5299],{"class":649},"  -H",[603,5301,5302],{"class":613}," '",[603,5304,5305],{"class":649},"content-type: application\u002Fjson",[603,5307,5308],{"class":613},"'",[603,5310,5294],{"class":617},[603,5312,5313,5316,5318,5321],{"class":605,"line":688},[603,5314,5315],{"class":649},"  -d",[603,5317,5302],{"class":613},[603,5319,5320],{"class":649},"{\"email\":\"alice@example.com\",\"password\":\"correct-horse-battery\"}",[603,5322,5323],{"class":613},"'\n",[603,5325,5326],{"class":605,"line":705},[603,5327,685],{"emptyLinePlaceholder":684},[603,5329,5330,5333,5336,5338,5340,5342,5344,5347],{"class":605,"line":712},[603,5331,5332],{"class":617},"TOKEN",[603,5334,5335],{"class":613},"=$(",[603,5337,5279],{"class":698},[603,5339,5282],{"class":649},[603,5341,5285],{"class":649},[603,5343,5288],{"class":649},[603,5345,5346],{"class":649}," localhost:3000\u002Fauth\u002Flogin",[603,5348,5294],{"class":617},[603,5350,5351,5353,5355,5357,5359],{"class":605,"line":718},[603,5352,5299],{"class":649},[603,5354,5302],{"class":613},[603,5356,5305],{"class":649},[603,5358,5308],{"class":613},[603,5360,5294],{"class":617},[603,5362,5363,5365,5367,5369,5371,5373,5376,5379,5382],{"class":605,"line":752},[603,5364,5315],{"class":649},[603,5366,5302],{"class":613},[603,5368,5320],{"class":649},[603,5370,5308],{"class":613},[603,5372,1576],{"class":613},[603,5374,5375],{"class":698}," jq",[603,5377,5378],{"class":649}," -r",[603,5380,5381],{"class":649}," .accessToken",[603,5383,3458],{"class":613},[603,5385,5386],{"class":605,"line":779},[603,5387,685],{"emptyLinePlaceholder":684},[603,5389,5390],{"class":605,"line":784},[603,5391,5392],{"class":708},"# Call a protected route with the bearer token\n",[603,5394,5395,5397,5399,5402,5405,5407,5410,5413],{"class":605,"line":814},[603,5396,5279],{"class":698},[603,5398,5282],{"class":649},[603,5400,5401],{"class":649}," localhost:3000\u002Fauth\u002Fme",[603,5403,5404],{"class":649}," -H",[603,5406,646],{"class":613},[603,5408,5409],{"class":649},"authorization: Bearer ",[603,5411,5412],{"class":617},"$TOKEN",[603,5414,5415],{"class":613},"\"\n",[603,5417,5418],{"class":605,"line":841},[603,5419,5420],{"class":708},"# -> 200 with the caller's profile\n",[603,5422,5423],{"class":605,"line":846},[603,5424,685],{"emptyLinePlaceholder":684},[603,5426,5427,5429,5431,5434,5436,5438,5440,5442],{"class":605,"line":870},[603,5428,5279],{"class":698},[603,5430,5282],{"class":649},[603,5432,5433],{"class":649}," localhost:3000\u002Fadmin\u002Fstats",[603,5435,5404],{"class":649},[603,5437,646],{"class":613},[603,5439,5409],{"class":649},[603,5441,5412],{"class":617},[603,5443,5415],{"class":613},[603,5445,5446],{"class":605,"line":890},[603,5447,5448],{"class":708},"# -> 403: alice holds profile:read, not admin:stats:read\n",[578,5450,5452],{"id":5451},"pitfalls","Pitfalls",[499,5454,5455,588,5458,5460,5461,5464,5465,5468,5469,883],{},[518,5456,5457],{},"The guard 401s every request because it runs before the auth middleware.",[509,5459,4991],{}," reads the\nprincipal the auth middleware stores; if the guard runs first the slot is empty, and every guarded route\ncomes back ",[509,5462,5463],{},"401 Authentication is required"," even with a valid token. Middleware runs in list order, so put\nthe auth middleware ",[518,5466,5467],{},"before"," the guard: ",[509,5470,5471],{},"middlewares: [JWTAuthMiddleware, GuardMiddleware]",[499,5473,5474,588,5477,5479,5480,5482,5483,5485,5486,5488,5489,5492,5493,5495,5496,5498],{},[518,5475,5476],{},"Assuming every framework class needs a wrapper.",[509,5478,538],{}," and other shipped classes whose\nconstructor is plain DI tokens are bare providers — list them directly, as in step 4. ",[509,5481,570],{}," is only\nneeded where a class reads a ",[573,5484,3895],{}," of a dependency, calls a ",[573,5487,3902],{},", or takes a ",[573,5490,5491],{},"non-DI","\nconstructor argument (",[509,5494,529],{},"'s ",[509,5497,3908],{},") — the compiler can't express any of those from a\nconstructor parameter type alone.",[499,5500,5501,5508,5509,5511,5512,5515,5516,5519],{},[518,5502,5503,5504,5507],{},"Scopes are typed ",[509,5505,5506],{},"string[]"," until you narrow them."," A scope is just a ",[509,5510,1419],{},", so a typo in\n",[509,5513,5514],{},"\"admin:stats:read\""," won't be caught. Pin the set to your app's permission codes by declaration-merging the\nglobal seam — ",[509,5517,5518],{},"declare global { interface RequestDefaults { scopes: MyPermissionCodes } }"," — and every\nscope string is then checked at compile time.",[578,5521,5523],{"id":5522},"see-also","See also",[503,5525,5526,5534,5549,5558,5563],{},[506,5527,5528,5530,5531,5533],{},[1262,5529,97],{"href":98}," — how JWT signing, verification, and the request-scoped\nprincipal fit together, with the full ",[509,5532,529],{}," and key-handling surface.",[506,5535,5536,5539,5540,5542,5543,5545,5546,5548],{},[1262,5537,5538],{"href":103},"Permissions"," — declaring ",[509,5541,5136],{}," on contract routes and enforcing them with\n",[509,5544,4991],{}," versus an explicit ",[509,5547,4711],{}," check.",[506,5550,5551,5553,5554,5557],{},[1262,5552,57],{"href":58}," — how a ",[509,5555,5556],{},"middleware"," chain nests broad to narrow, and why auth\nmust run before the guard.",[506,5559,5560,5562],{},[1262,5561,453],{"href":454}," — apply this middleware and these scopes to a typed,\nDrizzle-persisted CRUD feature end-to-end.",[506,5564,5565,5573,5574,5576,5577,5576,5579,5581],{},[1262,5566,5570],{"href":5567,"rel":5568},"https:\u002F\u002Fgithub.com\u002Fheximon\u002Fheximon\u002Ftree\u002Fmain\u002Fexamples\u002Fladder\u002FL07-auth",[5569],"nofollow",[518,5571,5572],{},"the ladder's L07 — Auth","\n— the runnable identity service this recipe is drawn from, with an end-to-end test proving the full\n",[509,5575,551],{}," \u002F ",[509,5578,555],{},[509,5580,559],{}," scope ladder over the real compiler.",[5583,5584,5585],"style",{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":599,"searchDepth":658,"depth":658,"links":5587},[5588,5589,5590,5591,5592,5593,5594,5595],{"id":580,"depth":658,"text":581},{"id":1268,"depth":658,"text":1269},{"id":1767,"depth":658,"text":1768},{"id":3867,"depth":658,"text":3868},{"id":4420,"depth":658,"text":4421},{"id":5253,"depth":658,"text":5254},{"id":5451,"depth":658,"text":5452},{"id":5522,"depth":658,"text":5523},"Protect routes end-to-end with IdentityKeyPair, JWTFactory, JWTVerifier, PasswordHashingAlgorithm, AuthContext, JWTAuthMiddleware, and scope guards — register, login, and a scope-guarded endpoint.","md",null,{},{"icon":100},{"title":457,"description":5596},"dDXFpPoRLeIQosal1nY9wfkyvrA8_Nwla0O8z88xzsM",[5604,5606],{"title":453,"path":454,"stem":455,"description":5605,"icon":80,"children":-1},"A typed CRUD feature end-to-end — a zod-validated body, an inline-route Controller, a plain repository over Drizzle, boot-time table creation, and an in-process test.",{"title":461,"path":462,"stem":463,"description":5607,"icon":464,"children":-1},"Move work off the request path with a typed IntegrationEvent producer, an IntegrationEventHandler consumer, an in-process MemoryIntegrationEventTransport default, and an optional transactional-outbox tier for at-least-once delivery.",1783300761903]