[{"data":1,"prerenderedAt":3847},["ShallowReactive",2],{"navigation_docs":3,"-guide-concepts-auth-and-scopes":587,"-guide-concepts-auth-and-scopes-surround":3842},[4,50,155,441,504],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":49},"Get Started","i-lucide-rocket","\u002Fget-started","1.get-started",[10,15,20,25,30,35,40,45],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fget-started\u002Fintroduction","1.get-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fget-started\u002Finstallation","1.get-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Project structure","\u002Fget-started\u002Fproject-structure","1.get-started\u002F3.project-structure","i-lucide-folder-tree",{"title":26,"path":27,"stem":28,"icon":29},"Configuration","\u002Fget-started\u002Fconfiguration","1.get-started\u002F4.configuration","i-lucide-settings",{"title":31,"path":32,"stem":33,"icon":34},"Authentication","\u002Fget-started\u002Fauthentication","1.get-started\u002F5.authentication","i-lucide-lock",{"title":36,"path":37,"stem":38,"icon":39},"Adding modules","\u002Fget-started\u002Fadding-modules","1.get-started\u002F6.adding-modules","i-lucide-puzzle",{"title":41,"path":42,"stem":43,"icon":44},"Database","\u002Fget-started\u002Fdatabase","1.get-started\u002F7.database","i-lucide-database",{"title":46,"path":47,"stem":48,"icon":6},"Deployment","\u002Fget-started\u002Fdeployment","1.get-started\u002F8.deployment",false,{"title":51,"icon":52,"path":53,"stem":54,"children":55,"page":49},"Guide","i-lucide-book-open","\u002Fguide","2.guide",[56,90,115,125],{"title":57,"icon":49,"path":58,"stem":59,"children":60,"page":49},"Concepts","\u002Fguide\u002Fconcepts","2.guide\u002F1.concepts",[61,66,71,76,80,85],{"title":62,"path":63,"stem":64,"icon":65},"Architecture","\u002Fguide\u002Fconcepts\u002Farchitecture","2.guide\u002F1.concepts\u002F1.architecture","i-lucide-blocks",{"title":67,"path":68,"stem":69,"icon":70},"Access control","\u002Fguide\u002Fconcepts\u002Fauth-and-scopes","2.guide\u002F1.concepts\u002F2.auth-and-scopes","i-lucide-shield-check",{"title":72,"path":73,"stem":74,"icon":75},"Extension points","\u002Fguide\u002Fconcepts\u002Fextension-points","2.guide\u002F1.concepts\u002F3.extension-points","i-lucide-plug",{"title":77,"path":78,"stem":79,"icon":44},"Database extensions","\u002Fguide\u002Fconcepts\u002Fdatabase-extensions","2.guide\u002F1.concepts\u002F4.database-extensions",{"title":81,"path":82,"stem":83,"icon":84},"Identity","\u002Fguide\u002Fconcepts\u002Fidentity","2.guide\u002F1.concepts\u002F5.identity","i-lucide-fingerprint",{"title":86,"path":87,"stem":88,"icon":89},"Glossary","\u002Fguide\u002Fconcepts\u002Fglossary","2.guide\u002F1.concepts\u002F6.glossary","i-lucide-book-a",{"title":91,"icon":49,"path":92,"stem":93,"children":94,"page":49},"UX patterns","\u002Fguide\u002Fux-patterns","2.guide\u002F2.ux-patterns",[95,100,105,110],{"title":96,"path":97,"stem":98,"icon":99},"Layouts and navigation","\u002Fguide\u002Fux-patterns\u002Flayouts-and-navigation","2.guide\u002F2.ux-patterns\u002F1.layouts-and-navigation","i-lucide-layout-template",{"title":101,"path":102,"stem":103,"icon":104},"Pages","\u002Fguide\u002Fux-patterns\u002Fpages","2.guide\u002F2.ux-patterns\u002F2.pages","i-lucide-table",{"title":106,"path":107,"stem":108,"icon":109},"Forms and actions","\u002Fguide\u002Fux-patterns\u002Fforms-and-actions","2.guide\u002F2.ux-patterns\u002F3.forms-and-actions","i-lucide-square-pen",{"title":111,"path":112,"stem":113,"icon":114},"Text and formatting","\u002Fguide\u002Fux-patterns\u002Ftext-and-formatting","2.guide\u002F2.ux-patterns\u002F4.text-and-formatting","i-lucide-type",{"title":116,"icon":49,"path":117,"stem":118,"children":119,"page":49},"Best practices","\u002Fguide\u002Fbest-practices","2.guide\u002F3.best-practices",[120],{"title":121,"path":122,"stem":123,"icon":124},"HTTP routes","\u002Fguide\u002Fbest-practices\u002Fhttp-routes","2.guide\u002F3.best-practices\u002F1.http-routes","i-lucide-route",{"title":126,"icon":49,"path":127,"stem":128,"children":129,"page":49},"Going further","\u002Fguide\u002Fgoing-further","2.guide\u002F4.going-further",[130,135,140,145,150],{"title":131,"path":132,"stem":133,"icon":134},"Use your own OAuth server","\u002Fguide\u002Fgoing-further\u002Fown-oauth-server","2.guide\u002F4.going-further\u002F1.own-oauth-server","i-lucide-key-round",{"title":136,"path":137,"stem":138,"icon":139},"Create an OAuth application","\u002Fguide\u002Fgoing-further\u002Foauth-application","2.guide\u002F4.going-further\u002F2.oauth-application","i-lucide-app-window",{"title":141,"path":142,"stem":143,"icon":144},"Signing keys","\u002Fguide\u002Fgoing-further\u002Fsigning-keys","2.guide\u002F4.going-further\u002F3.signing-keys","i-lucide-key-square",{"title":146,"path":147,"stem":148,"icon":149},"Stripe setup","\u002Fguide\u002Fgoing-further\u002Fstripe-setup","2.guide\u002F4.going-further\u002F4.stripe-setup","i-lucide-credit-card",{"title":151,"path":152,"stem":153,"icon":154},"Migrate to the permission model","\u002Fguide\u002Fgoing-further\u002Fmigrate-access-control","2.guide\u002F4.going-further\u002F5.migrate-access-control","i-lucide-arrow-right-left",{"title":156,"icon":157,"path":158,"stem":159,"children":160},"Modules","i-lucide-boxes","\u002Fmodules","3.modules",[161,163,244,273,320,375],{"title":156,"path":158,"stem":162},"3.modules\u002Findex",{"title":81,"icon":49,"path":164,"stem":165,"children":166,"page":49},"\u002Fmodules\u002Fidentity","3.modules\u002F1.identity",[167,190,194,198,220,224,240],{"title":168,"path":169,"stem":170,"children":171},"Auth","\u002Fmodules\u002Fidentity\u002Fauth","3.modules\u002F1.identity\u002F1.auth\u002Findex",[172,174,178,182,186],{"title":173,"path":169,"stem":170},"Overview",{"title":175,"path":176,"stem":177},"Server API","\u002Fmodules\u002Fidentity\u002Fauth\u002Fserver","3.modules\u002F1.identity\u002F1.auth\u002F2.server",{"title":179,"path":180,"stem":181},"Reference","\u002Fmodules\u002Fidentity\u002Fauth\u002Freference","3.modules\u002F1.identity\u002F1.auth\u002F3.reference",{"title":183,"path":184,"stem":185},"Client mode","\u002Fmodules\u002Fidentity\u002Fauth\u002Fclient-mode","3.modules\u002F1.identity\u002F1.auth\u002F4.client-mode",{"title":187,"path":188,"stem":189},"OAuth providers","\u002Fmodules\u002Fidentity\u002Fauth\u002Foauth-providers","3.modules\u002F1.identity\u002F1.auth\u002F5.oauth-providers",{"title":191,"path":192,"stem":193},"Users","\u002Fmodules\u002Fidentity\u002Fusers","3.modules\u002F1.identity\u002F2.users",{"title":195,"path":196,"stem":197},"Account","\u002Fmodules\u002Fidentity\u002Faccount","3.modules\u002F1.identity\u002F3.account",{"title":199,"path":200,"stem":201,"children":202},"Authorization","\u002Fmodules\u002Fidentity\u002Fauthorization","3.modules\u002F1.identity\u002F4.authorization\u002Findex",[203,204,208,212,216],{"title":173,"path":200,"stem":201},{"title":205,"path":206,"stem":207},"Roles and requirements","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Froles-and-requirements","3.modules\u002F1.identity\u002F4.authorization\u002F2.roles-and-requirements",{"title":209,"path":210,"stem":211},"Policies","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fpolicies","3.modules\u002F1.identity\u002F4.authorization\u002F3.policies",{"title":213,"path":214,"stem":215},"Client","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fclient","3.modules\u002F1.identity\u002F4.authorization\u002F4.client",{"title":217,"path":218,"stem":219},"Admin","\u002Fmodules\u002Fidentity\u002Fauthorization\u002Fadmin","3.modules\u002F1.identity\u002F4.authorization\u002F5.admin",{"title":221,"path":222,"stem":223},"Preferences","\u002Fmodules\u002Fidentity\u002Fpreferences","3.modules\u002F1.identity\u002F5.preferences",{"title":225,"path":226,"stem":227,"children":228},"OAuth Server","\u002Fmodules\u002Fidentity\u002Foauth-server","3.modules\u002F1.identity\u002F6.oauth-server\u002Findex",[229,230,233,237],{"title":173,"path":226,"stem":227},{"title":175,"path":231,"stem":232},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fserver","3.modules\u002F1.identity\u002F6.oauth-server\u002F2.server",{"title":234,"path":235,"stem":236},"Hooks","\u002Fmodules\u002Fidentity\u002Foauth-server\u002Fhooks","3.modules\u002F1.identity\u002F6.oauth-server\u002F3.hooks",{"title":179,"path":238,"stem":239},"\u002Fmodules\u002Fidentity\u002Foauth-server\u002Froutes-and-clients","3.modules\u002F1.identity\u002F6.oauth-server\u002F4.routes-and-clients",{"title":241,"path":242,"stem":243},"OAuth Resource","\u002Fmodules\u002Fidentity\u002Foauth-resource","3.modules\u002F1.identity\u002F8.oauth-resource",{"title":245,"icon":49,"path":246,"stem":247,"children":248,"page":49},"Commerce","\u002Fmodules\u002Fcommerce","3.modules\u002F2.commerce",[249,253,257],{"title":250,"path":251,"stem":252},"Stripe","\u002Fmodules\u002Fcommerce\u002Fstripe","3.modules\u002F2.commerce\u002F1.stripe",{"title":254,"path":255,"stem":256},"Payments","\u002Fmodules\u002Fcommerce\u002Fpayments","3.modules\u002F2.commerce\u002F2.payments",{"title":258,"path":259,"stem":260,"children":261},"Subscriptions","\u002Fmodules\u002Fcommerce\u002Fsubscriptions","3.modules\u002F2.commerce\u002F3.subscriptions\u002Findex",[262,263,266,270],{"title":173,"path":259,"stem":260},{"title":175,"path":264,"stem":265},"\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Fserver","3.modules\u002F2.commerce\u002F3.subscriptions\u002F2.server",{"title":267,"path":268,"stem":269},"App","\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Fapp","3.modules\u002F2.commerce\u002F3.subscriptions\u002F3.app",{"title":179,"path":271,"stem":272},"\u002Fmodules\u002Fcommerce\u002Fsubscriptions\u002Freference","3.modules\u002F2.commerce\u002F3.subscriptions\u002F4.reference",{"title":274,"icon":49,"path":275,"stem":276,"children":277,"page":49},"Features","\u002Fmodules\u002Ffeatures","3.modules\u002F3.features",[278,293,312,316],{"title":279,"path":280,"stem":281,"children":282},"Events","\u002Fmodules\u002Ffeatures\u002Fevents","3.modules\u002F3.features\u002F1.events\u002Findex",[283,284,287,290],{"title":173,"path":280,"stem":281},{"title":175,"path":285,"stem":286},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Fserver","3.modules\u002F3.features\u002F1.events\u002F2.server",{"title":267,"path":288,"stem":289},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Fapp","3.modules\u002F3.features\u002F1.events\u002F3.app",{"title":179,"path":291,"stem":292},"\u002Fmodules\u002Ffeatures\u002Fevents\u002Freference","3.modules\u002F3.features\u002F1.events\u002F4.reference",{"title":294,"path":295,"stem":296,"children":297},"Forms","\u002Fmodules\u002Ffeatures\u002Fforms","3.modules\u002F3.features\u002F2.forms\u002Findex",[298,299,303,306,309],{"title":173,"path":295,"stem":296},{"title":300,"path":301,"stem":302},"Definition","\u002Fmodules\u002Ffeatures\u002Fforms\u002Fdefinition","3.modules\u002F3.features\u002F2.forms\u002F2.definition",{"title":175,"path":304,"stem":305},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Fserver","3.modules\u002F3.features\u002F2.forms\u002F3.server",{"title":267,"path":307,"stem":308},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Fapp","3.modules\u002F3.features\u002F2.forms\u002F4.app",{"title":179,"path":310,"stem":311},"\u002Fmodules\u002Ffeatures\u002Fforms\u002Freference","3.modules\u002F3.features\u002F2.forms\u002F5.reference",{"title":313,"path":314,"stem":315},"Cookie Consent","\u002Fmodules\u002Ffeatures\u002Fcookie-consent","3.modules\u002F3.features\u002F3.cookie-consent",{"title":317,"path":318,"stem":319},"MCP","\u002Fmodules\u002Ffeatures\u002Fmcp","3.modules\u002F3.features\u002F4.mcp",{"title":321,"icon":49,"path":322,"stem":323,"children":324,"page":49},"Interface","\u002Fmodules\u002Finterface","3.modules\u002F4.interface",[325,350,353,357,361],{"title":326,"path":327,"stem":328,"children":329},"UI","\u002Fmodules\u002Finterface\u002Fui","3.modules\u002F4.interface\u002F1.ui\u002Findex",[330,331,335,339,343,347],{"title":173,"path":327,"stem":328},{"title":332,"path":333,"stem":334},"Page components","\u002Fmodules\u002Finterface\u002Fui\u002Fpage-components","3.modules\u002F4.interface\u002F1.ui\u002F2.page-components",{"title":336,"path":337,"stem":338},"App shell","\u002Fmodules\u002Finterface\u002Fui\u002Fapp-shell","3.modules\u002F4.interface\u002F1.ui\u002F3.app-shell",{"title":340,"path":341,"stem":342},"Form inputs","\u002Fmodules\u002Finterface\u002Fui\u002Fform-inputs","3.modules\u002F4.interface\u002F1.ui\u002F4.form-inputs",{"title":344,"path":345,"stem":346},"Composables and helpers","\u002Fmodules\u002Finterface\u002Fui\u002Fcomposables","3.modules\u002F4.interface\u002F1.ui\u002F5.composables",{"title":179,"path":348,"stem":349},"\u002Fmodules\u002Finterface\u002Fui\u002Freference","3.modules\u002F4.interface\u002F1.ui\u002F6.reference",{"title":217,"path":351,"stem":352},"\u002Fmodules\u002Finterface\u002Fadmin","3.modules\u002F4.interface\u002F2.admin",{"title":354,"path":355,"stem":356},"Menu","\u002Fmodules\u002Finterface\u002Fmenu","3.modules\u002F4.interface\u002F3.menu",{"title":358,"path":359,"stem":360},"Widget","\u002Fmodules\u002Finterface\u002Fwidget","3.modules\u002F4.interface\u002F4.widget",{"title":362,"path":363,"stem":364,"children":365},"Mobile","\u002Fmodules\u002Finterface\u002Fmobile","3.modules\u002F4.interface\u002F5.mobile\u002Findex",[366,367,371],{"title":173,"path":363,"stem":364},{"title":368,"path":369,"stem":370},"CLI and devices","\u002Fmodules\u002Finterface\u002Fmobile\u002Fcli-and-devices","3.modules\u002F4.interface\u002F5.mobile\u002F2.cli-and-devices",{"title":372,"path":373,"stem":374},"Splash screen","\u002Fmodules\u002Finterface\u002Fmobile\u002Fsplash-screen","3.modules\u002F4.interface\u002F5.mobile\u002F3.splash-screen",{"title":376,"icon":49,"path":377,"stem":378,"children":379,"page":49},"Platform","\u002Fmodules\u002Fplatform","3.modules\u002F5.platform",[380,402,406,423],{"title":381,"path":382,"stem":383,"children":384},"Module Kit","\u002Fmodules\u002Fplatform\u002Fmodule-kit","3.modules\u002F5.platform\u002F1.module-kit\u002Findex",[385,386,390,394,398],{"title":173,"path":382,"stem":383},{"title":387,"path":388,"stem":389},"Server toolkit","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fserver-toolkit","3.modules\u002F5.platform\u002F1.module-kit\u002F2.server-toolkit",{"title":391,"path":392,"stem":393},"Registries and menus","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fregistries-and-menus","3.modules\u002F5.platform\u002F1.module-kit\u002F3.registries-and-menus",{"title":395,"path":396,"stem":397},"Translations","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fi18n","3.modules\u002F5.platform\u002F1.module-kit\u002F4.i18n",{"title":399,"path":400,"stem":401},"Database helpers","\u002Fmodules\u002Fplatform\u002Fmodule-kit\u002Fdatabase","3.modules\u002F5.platform\u002F1.module-kit\u002F5.database",{"title":403,"path":404,"stem":405},"Notify","\u002Fmodules\u002Fplatform\u002Fnotify","3.modules\u002F5.platform\u002F2.notify",{"title":407,"path":408,"stem":409,"children":410},"Email Templates","\u002Fmodules\u002Fplatform\u002Femail-templates","3.modules\u002F5.platform\u002F3.email-templates\u002Findex",[411,412,416,419],{"title":173,"path":408,"stem":409},{"title":413,"path":414,"stem":415},"Email components","\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fcomponents","3.modules\u002F5.platform\u002F3.email-templates\u002F2.components",{"title":175,"path":417,"stem":418},"\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fserver","3.modules\u002F5.platform\u002F3.email-templates\u002F3.server",{"title":420,"path":421,"stem":422},"Module emails","\u002Fmodules\u002Fplatform\u002Femail-templates\u002Fmodule-emails","3.modules\u002F5.platform\u002F3.email-templates\u002F4.module-emails",{"title":424,"path":425,"stem":426,"children":427},"Base Layer","\u002Fmodules\u002Fplatform\u002Fbase-layer","3.modules\u002F5.platform\u002F4.base-layer\u002Findex",[428,429,433,437],{"title":173,"path":425,"stem":426},{"title":430,"path":431,"stem":432},"Content collections","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Fcontent-collections","3.modules\u002F5.platform\u002F4.base-layer\u002F2.content-collections",{"title":434,"path":435,"stem":436},"Layouts and routing","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Flayouts-and-routing","3.modules\u002F5.platform\u002F4.base-layer\u002F3.layouts-and-routing",{"title":438,"path":439,"stem":440},"Composables and utils","\u002Fmodules\u002Fplatform\u002Fbase-layer\u002Fcomposables-and-utils","3.modules\u002F5.platform\u002F4.base-layer\u002F4.composables-and-utils",{"title":442,"icon":443,"path":444,"stem":445,"children":446,"page":49},"Examples","i-lucide-code-xml","\u002Fexamples","4.examples",[447,466,476],{"title":448,"icon":49,"path":449,"stem":450,"children":451,"page":49},"Starters","\u002Fexamples\u002Fstarters","4.examples\u002F1.starters",[452,456,461],{"title":453,"path":454,"stem":455,"icon":6},"Workspace starter","\u002Fexamples\u002Fstarters\u002Fworkspace","4.examples\u002F1.starters\u002F1.workspace",{"title":457,"path":458,"stem":459,"icon":460},"Mobile starter","\u002Fexamples\u002Fstarters\u002Fmobile","4.examples\u002F1.starters\u002F2.mobile","i-lucide-smartphone",{"title":462,"path":463,"stem":464,"icon":465},"Standalone mobile starter","\u002Fexamples\u002Fstarters\u002Fmobile-standalone","4.examples\u002F1.starters\u002F3.mobile-standalone","i-lucide-tablet-smartphone",{"title":467,"icon":49,"path":468,"stem":469,"children":470,"page":49},"Apps","\u002Fexamples\u002Fapps","4.examples\u002F2.apps",[471],{"title":472,"path":473,"stem":474,"icon":475},"Frigear website app","\u002Fexamples\u002Fapps\u002Ffrigear-nu-website","4.examples\u002F2.apps\u002F1.frigear-nu-website","i-lucide-layout-dashboard",{"title":477,"icon":49,"path":478,"stem":479,"children":480,"page":49},"Tasks","\u002Fexamples\u002Ftasks","4.examples\u002F3.tasks",[481,485,489,494,499],{"title":482,"path":483,"stem":484,"icon":70},"Protect a route","\u002Fexamples\u002Ftasks\u002Fprotect-a-route","4.examples\u002F3.tasks\u002F1.protect-a-route",{"title":486,"path":487,"stem":488,"icon":475},"Add an admin page","\u002Fexamples\u002Ftasks\u002Fadd-an-admin-page","4.examples\u002F3.tasks\u002F2.add-an-admin-page",{"title":490,"path":491,"stem":492,"icon":493},"Sell event tickets","\u002Fexamples\u002Ftasks\u002Fsell-event-tickets","4.examples\u002F3.tasks\u002F3.sell-event-tickets","i-lucide-ticket",{"title":495,"path":496,"stem":497,"icon":498},"Build a form","\u002Fexamples\u002Ftasks\u002Fbuild-a-form","4.examples\u002F3.tasks\u002F4.build-a-form","i-lucide-clipboard-list",{"title":500,"path":501,"stem":502,"icon":503},"Send an email","\u002Fexamples\u002Ftasks\u002Fsend-an-email","4.examples\u002F3.tasks\u002F5.send-an-email","i-lucide-mail",{"title":505,"icon":506,"path":507,"stem":508,"children":509,"page":49},"Contribute","i-lucide-git-pull-request","\u002Fcontribute","5.contribute",[510,513,518,544,549,552,556,571],{"title":173,"path":511,"stem":512,"icon":506},"\u002Fcontribute\u002Foverview","5.contribute\u002F1.overview",{"title":514,"path":515,"stem":516,"icon":517},"Local development","\u002Fcontribute\u002Flocal-development","5.contribute\u002F2.local-development","i-lucide-terminal",{"title":519,"icon":49,"path":520,"stem":521,"children":522,"page":49},"Writing a module","\u002Fcontribute\u002Fwriting-a-module","5.contribute\u002F3.writing-a-module",[523,527,532,536,541],{"title":524,"path":525,"stem":526,"icon":39},"Scaffold a module","\u002Fcontribute\u002Fwriting-a-module\u002Fscaffold","5.contribute\u002F3.writing-a-module\u002F1.scaffold",{"title":528,"path":529,"stem":530,"icon":531},"Setup checklist","\u002Fcontribute\u002Fwriting-a-module\u002Fsetup-checklist","5.contribute\u002F3.writing-a-module\u002F2.setup-checklist","i-lucide-list-checks",{"title":533,"path":534,"stem":535,"icon":44},"Database tables","\u002Fcontribute\u002Fwriting-a-module\u002Fdatabase-tables","5.contribute\u002F3.writing-a-module\u002F3.database-tables",{"title":537,"path":538,"stem":539,"icon":540},"Testing","\u002Fcontribute\u002Fwriting-a-module\u002Ftesting","5.contribute\u002F3.writing-a-module\u002F4.testing","i-lucide-flask-conical",{"title":67,"path":542,"stem":543,"icon":70},"\u002Fcontribute\u002Fwriting-a-module\u002Faccess-control","5.contribute\u002F3.writing-a-module\u002F5.access-control",{"title":545,"path":546,"stem":547,"icon":548},"Publishing","\u002Fcontribute\u002Fpublishing","5.contribute\u002F4.publishing","i-lucide-package-check",{"title":448,"path":550,"stem":551,"icon":6},"\u002Fcontribute\u002Fstarters","5.contribute\u002F5.starters",{"title":553,"path":554,"stem":555,"icon":52},"Documentation","\u002Fcontribute\u002Fdocumentation","5.contribute\u002F6.documentation",{"title":557,"icon":49,"path":558,"stem":559,"children":560,"page":49},"Tooling","\u002Fcontribute\u002Ftooling","5.contribute\u002F7.tooling",[561,566],{"title":562,"path":563,"stem":564,"icon":565},"CLI","\u002Fcontribute\u002Ftooling\u002Fcli","5.contribute\u002F7.tooling\u002F1.cli","i-lucide-square-terminal",{"title":567,"path":568,"stem":569,"icon":570},"ESLint config","\u002Fcontribute\u002Ftooling\u002Feslint-config","5.contribute\u002F7.tooling\u002F2.eslint-config","i-lucide-spell-check",{"title":572,"icon":49,"path":573,"stem":574,"children":575},"Decisions","\u002Fcontribute\u002Fdecisions","5.contribute\u002F8.decisions\u002Findex",[576,578,583],{"title":572,"path":573,"stem":574,"icon":577},"i-lucide-scale",{"title":579,"path":580,"stem":581,"icon":582},"Session lifetimes per OAuth client","\u002Fcontribute\u002Fdecisions\u002Foauth-token-lifetimes","5.contribute\u002F8.decisions\u002F1.oauth-token-lifetimes","i-lucide-timer",{"title":584,"path":585,"stem":586,"icon":70},"One permission model","\u002Fcontribute\u002Fdecisions\u002Fpermission-model","5.contribute\u002F8.decisions\u002F2.permission-model",{"id":588,"title":67,"body":589,"description":3835,"extension":3836,"links":3837,"meta":3838,"navigation":3839,"path":68,"seo":3840,"stem":69,"__hash__":3841},"docs\u002F2.guide\u002F1.concepts\u002F2.auth-and-scopes.md",{"type":590,"value":591,"toc":3811},"minimark",[592,596,610,615,789,793,796,899,902,967,971,979,983,994,1063,1069,1082,1090,1093,1099,1299,1306,1432,1489,1508,1512,1521,1626,1632,1636,1639,1778,1793,1796,1802,2248,2325,2345,2353,2363,2367,2393,2411,2415,2418,2420,2423,2529,2551,2568,2571,2580,2747,2759,2769,2772,2782,2798,2801,2804,2841,2854,2857,2861,2885,2940,2990,3023,3033,3036,3040,3046,3328,3490,3526,3534,3625,3629,3636,3668,3675,3684,3688,3698,3702,3712,3726,3738,3749,3771,3775,3807],[593,594,595],"p",{},"Every server route has to know who is calling and what they may do. A browser, a script and a mobile app each prove it\ndifferently: with a cookie, an API token or an access token. After this page you can decide which permission a route\nneeds, how a role or a token gets it, and why a check answers no.",[597,598,600,604,605,609],"callout",{"icon":599},"i-lucide-users",[601,602,603],"strong",{},"For:"," app developers and module authors. Why the model looks like this is in the\n",[606,607,608],"a",{"href":585},"decision record",".",[611,612,614],"h2",{"id":613},"which-one-do-i-use","Which one do I use?",[616,617,618,634],"table",{},[619,620,621],"thead",{},[622,623,624,628,631],"tr",{},[625,626,627],"th",{},"You want to",[625,629,630],{},"Use",[625,632,633],{},"Read",[635,636,637,654,674,688,703,719,735,749,766],"tbody",{},[622,638,639,642,648],{},[640,641,482],"td",{},[640,643,644],{},[645,646,647],"code",{},"await authorize(event, 'events:manage')",[640,649,650],{},[606,651,653],{"href":652},"#the-route-pattern","The route pattern",[622,655,656,659,668],{},[640,657,658],{},"Know who is calling, without a permission",[640,660,661,664,665],{},[645,662,663],{},"getAuth(event)"," or ",[645,666,667],{},"requireAuth(event)",[640,669,670],{},[606,671,673],{"href":672},"#the-principal","The principal",[622,675,676,679,684],{},[640,677,678],{},"Ask a yes or no, without throwing",[640,680,681],{},[645,682,683],{},"allows(event, permission)",[640,685,686],{},[606,687,653],{"href":652},[622,689,690,693,698],{},[640,691,692],{},"Refine a permission for one record (owner only)",[640,694,695],{},[645,696,697],{},"definePolicy",[640,699,700],{},[606,701,209],{"href":702},"#policies",[622,704,705,708,713],{},[640,706,707],{},"Override the answer from your host app",[640,709,710],{},[645,711,712],{},"authorization:before",[640,714,715],{},[606,716,718],{"href":717},"#the-before-hook","The before hook",[622,720,721,724,729],{},[640,722,723],{},"Give a role a permission",[640,725,726],{},[645,727,728],{},"authorization.roles",[640,730,731],{},[606,732,734],{"href":733},"#roles","Roles",[622,736,737,740,743],{},[640,738,739],{},"Let a token do something",[640,741,742],{},"A named scope, or the permission itself",[640,744,745],{},[606,746,748],{"href":747},"#scopes","Scopes",[622,750,751,754,760],{},[640,752,753],{},"Keep a permission away from tokens",[640,755,756,759],{},[645,757,758],{},"sessionOnly: true"," on the permission",[640,761,762],{},[606,763,765],{"href":764},"#sessiononly","sessionOnly",[622,767,768,771,783],{},[640,769,770],{},"Add permissions, roles or scopes from a module",[640,772,773,776,777,776,780],{},[645,774,775],{},"addPermission",", ",[645,778,779],{},"addRole",[645,781,782],{},"addScope",[640,784,785],{},[606,786,788],{"href":787},"#how-modules-contribute","How modules contribute",[611,790,792],{"id":791},"terms","Terms",[593,794,795],{},"One meaning per word. These five are the whole vocabulary:",[616,797,798,808],{},[619,799,800],{},[622,801,802,805],{},[625,803,804],{},"Term",[625,806,807],{},"Meaning",[635,809,810,830,845,873,885],{},[622,811,812,819],{},[640,813,814],{},[601,815,816],{},[606,817,818],{"href":87},"Permission",[640,820,821,822,825,826,829],{},"A ",[645,823,824],{},"resource:action"," string such as ",[645,827,828],{},"events:manage",", registered by a module or your host app. It is the only thing code checks.",[622,831,832,839],{},[640,833,834],{},[601,835,836],{},[606,837,838],{"href":87},"Role",[640,840,841,842,844],{},"A named bundle of permissions in ",[645,843,728],{},". A user holds several.",[622,846,847,854],{},[640,848,849],{},[601,850,851],{},[606,852,853],{"href":87},"Scope",[640,855,856,857,860,861,864,865,868,869,872],{},"What an API token or an OAuth access token may do: a permission, a wildcard, or a named scope that expands to permissions (",[645,858,859],{},"tickets"," is ",[645,862,863],{},"tickets:read"," and ",[645,866,867],{},"tickets:write",") or to none (",[645,870,871],{},"openid",").",[622,874,875,882],{},[640,876,877],{},[601,878,879],{},[606,880,881],{"href":87},"Policy",[640,883,884],{},"Code that refines one permission against a record. It can deny, never grant.",[622,886,887,894],{},[640,888,889],{},[601,890,891],{},[606,892,893],{"href":87},"Before hook",[640,895,896,898],{},[645,897,712],{},", the single override your host app answers.",[593,900,901],{},"A few more words are used throughout:",[903,904,905,917,925,933,941,957],"ul",{},[906,907,908,913,914,609],"li",{},[601,909,910,609],{},[606,911,912],{"href":87},"Principal"," Who made a request: the signed-in user, how they proved it and the\npermissions that proof carries. In code it is ",[645,915,916],{},"AuthPrincipal",[906,918,919,924],{},[601,920,921,609],{},[606,922,923],{"href":87},"Visitor"," Someone with no valid credential.",[906,926,927,932],{},[601,928,929,609],{},[606,930,931],{"href":87},"Session"," A sealed cookie that keeps a person signed in to one host app in server\nmode.",[906,934,935,940],{},[601,936,937,609],{},[606,938,939],{"href":87},"Access token"," A short-lived signed JSON Web Token (JWT) that an issuer gives an\nOAuth application for a signed-in user.",[906,942,943,948,949,952,953,956],{},[601,944,945,609],{},[606,946,947],{"href":87},"PII"," Personal data. Permissions such as ",[645,950,951],{},"events:pii"," guard it. They are\n",[601,954,955],{},"sensitive",": a wildcard never covers them.",[906,958,959,962,963,609],{},[601,960,961],{},"Alias."," An old permission name that a permission was split from. A holder of the old name also holds the new\none. See ",[606,964,966],{"href":965},"#aliases","Aliases",[611,968,970],{"id":969},"how-it-works","How it works",[593,972,973,975,976,978],{},[606,974,168],{"href":169}," answers \"who is calling?\" once, for every kind of credential, and owns the engine that\ndecides. ",[606,977,199],{"href":200}," adds roles: which permissions a user holds. Auth reaches\nroles only through a role provider that Authorization registers, so Auth works without it.",[980,981,673],"h3",{"id":982},"the-principal",[593,984,985,986,989,990,993],{},"Every request with a valid credential becomes a principal: a ",[645,987,988],{},"user",", a ",[645,991,992],{},"kind"," and the permissions the credential\ncarries. There are three kinds:",[616,995,996,1010],{},[619,997,998],{},[622,999,1000,1004,1007],{},[625,1001,1002],{},[645,1003,992],{},[625,1005,1006],{},"Credential",[625,1008,1009],{},"What it may do",[635,1011,1012,1025,1046],{},[622,1013,1014,1019,1022],{},[640,1015,1016],{},[645,1017,1018],{},"session",[640,1020,1021],{},"Sealed cookie from nuxt-auth-utils",[640,1023,1024],{},"Whatever the user's roles allow",[622,1026,1027,1032,1043],{},[640,1028,1029],{},[645,1030,1031],{},"user-token",[640,1033,1034,1035,1038,1039,1042],{},"An ",[606,1036,1037],{"href":87},"API token",": ",[645,1040,1041],{},"Authorization: Bearer fnu_…"," (prefix configurable), created by the user",[640,1044,1045],{},"The scopes chosen at creation, frozen into the token",[622,1047,1048,1053,1060],{},[640,1049,1050],{},[645,1051,1052],{},"oauth",[640,1054,1055,1056,1059],{},"An access token: ",[645,1057,1058],{},"Authorization: Bearer \u003CJWT>",", issued to an OAuth application",[640,1061,1062],{},"The scopes the user granted, frozen into the token",[593,1064,1065,1068],{},[601,1066,1067],{},"Sessions"," are for people in a browser. The cookie names the user, and every request reads that user again from the\ndatabase. A role change applies to the next request, and a deleted account ends the session.",[593,1070,1071,864,1074,1077,1078,1081],{},[601,1072,1073],{},"API tokens",[601,1075,1076],{},"access tokens"," carry a ",[645,1079,1080],{},"permissions"," claim: the scopes they were issued with, expanded and\nintersected with what the user could do at that moment. Every check against a bearer credential looks at that frozen\nset first, then at what the user holds now. So a token never does more than it was issued for, and never more than its\nuser may do today. The frozen set is also why Auth never has to look up scope definitions while serving a request.",[1083,1084,1085,1086,1089],"caution",{},"A request whose bearer token looks like one Auth knows (the ",[645,1087,1088],{},"fnu_"," prefix or a JWT) but is invalid, expired or revoked\nis unauthenticated. This holds even when it also carries a valid session cookie.",[980,1091,653],{"id":1092},"the-route-pattern",[593,1094,1095,1098],{},[645,1096,1097],{},"authorize"," is the one call a route makes to decide. It returns the principal, or throws.",[1100,1101,1106],"pre",{"className":1102,"code":1103,"language":1104,"meta":1105,"style":1105},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F server\u002Fapi\u002Fevents\u002F[id].patch.ts\nexport default defineEventHandler(async (event) => {\n  await authorize(event, 'events:manage') \u002F\u002F 401 or 403, before anything is looked up\n  const { id } = await getValidatedRouterParams(event, idParams().parse)\n  const ev = await loadEvent(id) \u002F\u002F then 404\n  await authorize(event, 'events:manage', ev) \u002F\u002F again with the record, so policies can refine\n  \u002F\u002F ...\n})\n","ts","",[645,1107,1108,1117,1156,1188,1232,1257,1285,1291],{"__ignoreMap":1105},[1109,1110,1113],"span",{"class":1111,"line":1112},"line",1,[1109,1114,1116],{"class":1115},"sHwdD","\u002F\u002F server\u002Fapi\u002Fevents\u002F[id].patch.ts\n",[1109,1118,1120,1124,1127,1131,1135,1139,1143,1147,1150,1153],{"class":1111,"line":1119},2,[1109,1121,1123],{"class":1122},"s7zQu","export",[1109,1125,1126],{"class":1122}," default",[1109,1128,1130],{"class":1129},"s2Zo4"," defineEventHandler",[1109,1132,1134],{"class":1133},"sTEyZ","(",[1109,1136,1138],{"class":1137},"spNyl","async",[1109,1140,1142],{"class":1141},"sMK4o"," (",[1109,1144,1146],{"class":1145},"sHdIc","event",[1109,1148,1149],{"class":1141},")",[1109,1151,1152],{"class":1137}," =>",[1109,1154,1155],{"class":1141}," {\n",[1109,1157,1159,1162,1165,1168,1170,1173,1176,1179,1182,1185],{"class":1111,"line":1158},3,[1109,1160,1161],{"class":1122},"  await",[1109,1163,1164],{"class":1129}," authorize",[1109,1166,1134],{"class":1167},"swJcz",[1109,1169,1146],{"class":1133},[1109,1171,1172],{"class":1141},",",[1109,1174,1175],{"class":1141}," '",[1109,1177,828],{"class":1178},"sfazB",[1109,1180,1181],{"class":1141},"'",[1109,1183,1184],{"class":1167},") ",[1109,1186,1187],{"class":1115},"\u002F\u002F 401 or 403, before anything is looked up\n",[1109,1189,1191,1194,1197,1200,1203,1206,1209,1212,1214,1216,1218,1221,1224,1226,1229],{"class":1111,"line":1190},4,[1109,1192,1193],{"class":1137},"  const",[1109,1195,1196],{"class":1141}," {",[1109,1198,1199],{"class":1133}," id",[1109,1201,1202],{"class":1141}," }",[1109,1204,1205],{"class":1141}," =",[1109,1207,1208],{"class":1122}," await",[1109,1210,1211],{"class":1129}," getValidatedRouterParams",[1109,1213,1134],{"class":1167},[1109,1215,1146],{"class":1133},[1109,1217,1172],{"class":1141},[1109,1219,1220],{"class":1129}," idParams",[1109,1222,1223],{"class":1167},"()",[1109,1225,609],{"class":1141},[1109,1227,1228],{"class":1133},"parse",[1109,1230,1231],{"class":1167},")\n",[1109,1233,1235,1237,1240,1242,1244,1247,1249,1252,1254],{"class":1111,"line":1234},5,[1109,1236,1193],{"class":1137},[1109,1238,1239],{"class":1133}," ev",[1109,1241,1205],{"class":1141},[1109,1243,1208],{"class":1122},[1109,1245,1246],{"class":1129}," loadEvent",[1109,1248,1134],{"class":1167},[1109,1250,1251],{"class":1133},"id",[1109,1253,1184],{"class":1167},[1109,1255,1256],{"class":1115},"\u002F\u002F then 404\n",[1109,1258,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282],{"class":1111,"line":1259},6,[1109,1261,1161],{"class":1122},[1109,1263,1164],{"class":1129},[1109,1265,1134],{"class":1167},[1109,1267,1146],{"class":1133},[1109,1269,1172],{"class":1141},[1109,1271,1175],{"class":1141},[1109,1273,828],{"class":1178},[1109,1275,1181],{"class":1141},[1109,1277,1172],{"class":1141},[1109,1279,1239],{"class":1133},[1109,1281,1184],{"class":1167},[1109,1283,1284],{"class":1115},"\u002F\u002F again with the record, so policies can refine\n",[1109,1286,1288],{"class":1111,"line":1287},7,[1109,1289,1290],{"class":1115},"  \u002F\u002F ...\n",[1109,1292,1294,1297],{"class":1111,"line":1293},8,[1109,1295,1296],{"class":1141},"}",[1109,1298,1231],{"class":1133},[593,1300,1301,1302,1305],{},"The first call answers the permission as such, so a visitor learns nothing about which ids exist. The second call\nadds the record, which runs the ",[606,1303,1304],{"href":702},"policies",". A route that has no record to load makes only the first call.",[616,1307,1308,1321],{},[619,1309,1310],{},[622,1311,1312,1315,1318],{},[625,1313,1314],{},"Helper",[625,1316,1317],{},"Returns",[625,1319,1320],{},"Use it when",[635,1322,1323,1336,1353,1380,1393,1405,1420],{},[622,1324,1325,1330,1333],{},[640,1326,1327],{},[645,1328,1329],{},"authorize(event, permission, record?)",[640,1331,1332],{},"The principal, or throws",[640,1334,1335],{},"The route needs the permission.",[622,1337,1338,1347,1350],{},[640,1339,1340,1343,1344],{},[645,1341,1342],{},"allows(event, permission, record?)"," \u002F ",[645,1345,1346],{},"denies(...)",[640,1348,1349],{},"A boolean, never throws",[640,1351,1352],{},"A menu, an MCP tool or a branch inside a route.",[622,1354,1355,1360,1365],{},[640,1356,1357],{},[645,1358,1359],{},"inspect(event, permission, record?)",[640,1361,1362],{},[645,1363,1364],{},"{ allowed, status, code, via }",[640,1366,1367,1368,860,1371,776,1374,664,1377,609],{},"You need to know why: ",[645,1369,1370],{},"via",[645,1372,1373],{},"before",[645,1375,1376],{},"role",[645,1378,1379],{},"policy",[622,1381,1382,1387,1390],{},[640,1383,1384],{},[645,1385,1386],{},"canGrantScope(event, scope)",[640,1388,1389],{},"A boolean",[640,1391,1392],{},"Minting a token or consenting to a scope.",[622,1394,1395,1400,1402],{},[640,1396,1397],{},[645,1398,1399],{},"userCan(event, userId, permission)",[640,1401,1389],{},[640,1403,1404],{},"Another user than the caller, such as an assignment check. Never for the caller.",[622,1406,1407,1411,1417],{},[640,1408,1409],{},[645,1410,663],{},[640,1412,1413,1414],{},"The principal or ",[645,1415,1416],{},"null",[640,1418,1419],{},"Sign-in is optional. Never throws.",[622,1421,1422,1427,1429],{},[640,1423,1424],{},[645,1425,1426],{},"requireAuth(event, { sessionOnly? })",[640,1428,673],{},[640,1430,1431],{},"The route needs a signed-in caller and no permission.",[616,1433,1434,1444],{},[619,1435,1436],{},[622,1437,1438,1441],{},[625,1439,1440],{},"Failure",[625,1442,1443],{},"Status and code",[635,1445,1446,1456,1469,1479],{},[622,1447,1448,1451],{},[640,1449,1450],{},"No valid credential",[640,1452,1453],{},[645,1454,1455],{},"401 unauthorized",[622,1457,1458,1464],{},[640,1459,1460,1461,1463],{},"The permission is ",[645,1462,765],{}," and the request used a token",[640,1465,1466],{},[645,1467,1468],{},"403 session_required",[622,1470,1471,1474],{},[640,1472,1473],{},"The token's frozen permissions do not cover it",[640,1475,1476],{},[645,1477,1478],{},"403 insufficient_scope",[622,1480,1481,1484],{},[640,1482,1483],{},"Anything else that says no",[640,1485,1486],{},[645,1487,1488],{},"403 forbidden",[593,1490,1491,1494,1495,1497,1498,664,1501,1504,1505,609],{},[645,1492,1493],{},"getUserSession"," is only for flows that are session by nature: sign-in, sign-out and OAuth consent. Use ",[645,1496,1097],{},",\n",[645,1499,1500],{},"requireAuth",[645,1502,1503],{},"requireUser",", never nuxt-auth-utils' ",[645,1506,1507],{},"requireUserSession",[980,1509,1511],{"id":1510},"how-a-check-is-decided","How a check is decided",[593,1513,1514,1516,1517,1520],{},[645,1515,1097],{}," gathers its inputs (roles, the hook, policies) and hands them to one pure function, ",[645,1518,1519],{},"decide",". It runs\nthese steps in order, and the first that answers wins:",[1522,1523,1524,1533,1540,1549,1556,1574,1599,1621],"ol",{},[906,1525,1526,1529,1530,1532],{},[601,1527,1528],{},"Unknown permission:"," ",[645,1531,1488],{},", and an error in development. A name nobody registered is a typo, not a\npermission.",[906,1534,1535,1529,1538,609],{},[601,1536,1537],{},"No principal:",[645,1539,1455],{},[906,1541,1542,1529,1547,609],{},[601,1543,1544,1546],{},[645,1545,765],{}," and a bearer credential:",[645,1548,1468],{},[906,1550,1551,1529,1554,609],{},[601,1552,1553],{},"A bearer whose frozen permissions do not cover the permission:",[645,1555,1478],{},[906,1557,1558,1565,1566,1569,1570,1573],{},[601,1559,1560,1561,1564],{},"The ",[606,1562,1563],{"href":717},"before hook",":"," any ",[645,1567,1568],{},"deny()",", or a listener that throws, denies. Otherwise any ",[645,1571,1572],{},"allow()","\nallows and skips steps 6 and 7.",[906,1575,1576,1579,1580,1583,1584,1587,1588,1591,1592,1595,1596,1598],{},[601,1577,1578],{},"Roles:"," the user's roles (assigned, default, ",[645,1581,1582],{},"auto"," and from ",[645,1585,1586],{},"authorization:roles","), minus those whose\nrequirements fail, closed over ",[645,1589,1590],{},"inherit",", plus the ",[645,1593,1594],{},"implicit"," permissions. A token from a foreign issuer uses its\nown ",[645,1597,1080],{}," claim here instead.",[906,1600,1601,1605,1606,1609,1610,1613,1614,864,1617,1620],{},[601,1602,1603,1564],{},[606,1604,209],{"href":702}," only for a check with a record (or a policy marked ",[645,1607,1608],{},"withoutRecord","). ",[645,1611,1612],{},"false"," or a throw\ndenies; ",[645,1615,1616],{},"true",[645,1618,1619],{},"undefined"," keep the result.",[906,1622,1623],{},[601,1624,1625],{},"The result.",[593,1627,1628,1629,1631],{},"Steps 3 and 4 always win. Neither the before hook nor a policy can widen a credential, and a token can never use a\n",[645,1630,765],{}," permission, whatever the user's roles are.",[980,1633,1635],{"id":1634},"fail-closed","Fail closed",[593,1637,1638],{},"When something is missing or goes wrong, the answer is no.",[616,1640,1641,1651],{},[619,1642,1643],{},[622,1644,1645,1648],{},[625,1646,1647],{},"Situation",[625,1649,1650],{},"Result",[635,1652,1653,1667,1677,1687,1695,1702,1715,1728,1742,1750,1764],{},[622,1654,1655,1661],{},[640,1656,1657,1660],{},[645,1658,1659],{},"@frigear-nu\u002Fauthorization"," is not installed",[640,1662,1663,1664,1666],{},"Only ",[645,1665,1594],{}," permissions apply, plus the before hook. Route rules are still enforced by Auth.",[622,1668,1669,1672],{},[640,1670,1671],{},"The permission is not registered",[640,1673,1674,1676],{},[645,1675,1488],{},", also for admins and also when the before hook allows.",[622,1678,1679,1684],{},[640,1680,821,1681,1683],{},[645,1682,1373],{}," listener throws",[640,1685,1686],{},"Denies.",[622,1688,1689,1692],{},[640,1690,1691],{},"A role requirement throws",[640,1693,1694],{},"The role does not count, nor does a role that inherits it.",[622,1696,1697,1700],{},[640,1698,1699],{},"A policy throws, or a shared policy returns a promise",[640,1701,1686],{},[622,1703,1704,1710],{},[640,1705,1706,1707,1709],{},"A bearer credential without a frozen ",[645,1708,1080],{}," set",[640,1711,1712,1713,609],{},"Holds nothing: ",[645,1714,1478],{},[622,1716,1717,1723],{},[640,1718,1719,1720,1722],{},"A bearer on a ",[645,1721,765],{}," permission",[640,1724,1725,1727],{},[645,1726,1468],{},", even for an administrator.",[622,1729,1730,1739],{},[640,1731,1732,664,1735,1738],{},[645,1733,1734],{},"*",[645,1736,1737],{},"resource:*"," and a sensitive permission",[640,1740,1741],{},"Not covered. Grant it by name.",[622,1743,1744,1747],{},[640,1745,1746],{},"A role in the data that is not in config",[640,1748,1749],{},"Ignored at run time, and listed in a startup warning.",[622,1751,1752,1758],{},[640,1753,1754,1755],{},"An unknown scope at ",[645,1756,1757],{},"\u002Fauthorize",[640,1759,1760,1763],{},[645,1761,1762],{},"invalid_scope",". A refresh silently drops what is no longer allowed.",[622,1765,1766,1775],{},[640,1767,1768,1769,1771,1772,1774],{},"A grant, wildcard, requirement or ",[645,1770,1590],{}," that names nothing, an inheritance cycle, or an ",[645,1773,1582],{}," default role",[640,1776,1777],{},"A build error.",[1779,1780,1781,1782,1784,1785,664,1787,1790,1791,609],"note",{},"Without Authorization, a signed-in user can still use ",[645,1783,1594],{}," permissions: the self-service ones every user needs,\nsuch as ",[645,1786,863],{},[645,1788,1789],{},"profile:write",". Nothing else is allowed until you install it or answer the\n",[606,1792,1563],{"href":717},[980,1794,734],{"id":1795},"roles",[593,1797,1798,1799,1801],{},"A role is a named bundle, defined in ",[645,1800,728],{}," and by modules, and assigned to users in the database.",[1100,1803,1806],{"className":1102,"code":1804,"filename":1805,"language":1104,"meta":1105,"style":1105},"export default defineNuxtConfig({\n  authorization: {\n    defaultRoles: ['user'], \u002F\u002F every signed-in user holds these\n    roles: {\n      admin: {\n        label: 'Admin',\n        grant: ['*'], \u002F\u002F never covers sensitive permissions: grant `forms:pii` and friends by name\n      },\n      manager: {\n        label: 'Coordinator',\n        grant: ['forms:*', 'users:read', 'users:list'],\n        inherit: ['eventmanager', 'crew'],\n      },\n      eventmanager: { label: 'Event Manager', grant: ['events:*'], inherit: ['user'] },\n      member: {\n        label: 'Member',\n        grant: ['member-area:view'],\n        requirements: { subscription: '*' }, \u002F\u002F counts only while every requirement holds\n        auto: true, \u002F\u002F held by everyone who meets the requirements; not assignable\n        inherit: ['user'],\n      },\n      user: { label: 'User', grant: ['events:read'] },\n    },\n  },\n})\n","nuxt.config.ts",[645,1807,1808,1822,1831,1855,1864,1873,1888,1910,1915,1925,1941,1979,2009,2014,2075,2085,2101,2121,2148,2165,2184,2189,2229,2235,2241],{"__ignoreMap":1105},[1109,1809,1810,1812,1814,1817,1819],{"class":1111,"line":1112},[1109,1811,1123],{"class":1122},[1109,1813,1126],{"class":1122},[1109,1815,1816],{"class":1129}," defineNuxtConfig",[1109,1818,1134],{"class":1133},[1109,1820,1821],{"class":1141},"{\n",[1109,1823,1824,1827,1829],{"class":1111,"line":1119},[1109,1825,1826],{"class":1167},"  authorization",[1109,1828,1564],{"class":1141},[1109,1830,1155],{"class":1141},[1109,1832,1833,1836,1838,1841,1843,1845,1847,1850,1852],{"class":1111,"line":1158},[1109,1834,1835],{"class":1167},"    defaultRoles",[1109,1837,1564],{"class":1141},[1109,1839,1840],{"class":1133}," [",[1109,1842,1181],{"class":1141},[1109,1844,988],{"class":1178},[1109,1846,1181],{"class":1141},[1109,1848,1849],{"class":1133},"]",[1109,1851,1172],{"class":1141},[1109,1853,1854],{"class":1115}," \u002F\u002F every signed-in user holds these\n",[1109,1856,1857,1860,1862],{"class":1111,"line":1190},[1109,1858,1859],{"class":1167},"    roles",[1109,1861,1564],{"class":1141},[1109,1863,1155],{"class":1141},[1109,1865,1866,1869,1871],{"class":1111,"line":1234},[1109,1867,1868],{"class":1167},"      admin",[1109,1870,1564],{"class":1141},[1109,1872,1155],{"class":1141},[1109,1874,1875,1878,1880,1882,1884,1886],{"class":1111,"line":1259},[1109,1876,1877],{"class":1167},"        label",[1109,1879,1564],{"class":1141},[1109,1881,1175],{"class":1141},[1109,1883,217],{"class":1178},[1109,1885,1181],{"class":1141},[1109,1887,1497],{"class":1141},[1109,1889,1890,1893,1895,1897,1899,1901,1903,1905,1907],{"class":1111,"line":1287},[1109,1891,1892],{"class":1167},"        grant",[1109,1894,1564],{"class":1141},[1109,1896,1840],{"class":1133},[1109,1898,1181],{"class":1141},[1109,1900,1734],{"class":1178},[1109,1902,1181],{"class":1141},[1109,1904,1849],{"class":1133},[1109,1906,1172],{"class":1141},[1109,1908,1909],{"class":1115}," \u002F\u002F never covers sensitive permissions: grant `forms:pii` and friends by name\n",[1109,1911,1912],{"class":1111,"line":1293},[1109,1913,1914],{"class":1141},"      },\n",[1109,1916,1918,1921,1923],{"class":1111,"line":1917},9,[1109,1919,1920],{"class":1167},"      manager",[1109,1922,1564],{"class":1141},[1109,1924,1155],{"class":1141},[1109,1926,1928,1930,1932,1934,1937,1939],{"class":1111,"line":1927},10,[1109,1929,1877],{"class":1167},[1109,1931,1564],{"class":1141},[1109,1933,1175],{"class":1141},[1109,1935,1936],{"class":1178},"Coordinator",[1109,1938,1181],{"class":1141},[1109,1940,1497],{"class":1141},[1109,1942,1944,1946,1948,1950,1952,1955,1957,1959,1961,1964,1966,1968,1970,1973,1975,1977],{"class":1111,"line":1943},11,[1109,1945,1892],{"class":1167},[1109,1947,1564],{"class":1141},[1109,1949,1840],{"class":1133},[1109,1951,1181],{"class":1141},[1109,1953,1954],{"class":1178},"forms:*",[1109,1956,1181],{"class":1141},[1109,1958,1172],{"class":1141},[1109,1960,1175],{"class":1141},[1109,1962,1963],{"class":1178},"users:read",[1109,1965,1181],{"class":1141},[1109,1967,1172],{"class":1141},[1109,1969,1175],{"class":1141},[1109,1971,1972],{"class":1178},"users:list",[1109,1974,1181],{"class":1141},[1109,1976,1849],{"class":1133},[1109,1978,1497],{"class":1141},[1109,1980,1982,1985,1987,1989,1991,1994,1996,1998,2000,2003,2005,2007],{"class":1111,"line":1981},12,[1109,1983,1984],{"class":1167},"        inherit",[1109,1986,1564],{"class":1141},[1109,1988,1840],{"class":1133},[1109,1990,1181],{"class":1141},[1109,1992,1993],{"class":1178},"eventmanager",[1109,1995,1181],{"class":1141},[1109,1997,1172],{"class":1141},[1109,1999,1175],{"class":1141},[1109,2001,2002],{"class":1178},"crew",[1109,2004,1181],{"class":1141},[1109,2006,1849],{"class":1133},[1109,2008,1497],{"class":1141},[1109,2010,2012],{"class":1111,"line":2011},13,[1109,2013,1914],{"class":1141},[1109,2015,2017,2020,2022,2024,2027,2029,2031,2034,2036,2038,2041,2043,2045,2047,2050,2052,2054,2056,2059,2061,2063,2065,2067,2069,2072],{"class":1111,"line":2016},14,[1109,2018,2019],{"class":1167},"      eventmanager",[1109,2021,1564],{"class":1141},[1109,2023,1196],{"class":1141},[1109,2025,2026],{"class":1167}," label",[1109,2028,1564],{"class":1141},[1109,2030,1175],{"class":1141},[1109,2032,2033],{"class":1178},"Event Manager",[1109,2035,1181],{"class":1141},[1109,2037,1172],{"class":1141},[1109,2039,2040],{"class":1167}," grant",[1109,2042,1564],{"class":1141},[1109,2044,1840],{"class":1133},[1109,2046,1181],{"class":1141},[1109,2048,2049],{"class":1178},"events:*",[1109,2051,1181],{"class":1141},[1109,2053,1849],{"class":1133},[1109,2055,1172],{"class":1141},[1109,2057,2058],{"class":1167}," inherit",[1109,2060,1564],{"class":1141},[1109,2062,1840],{"class":1133},[1109,2064,1181],{"class":1141},[1109,2066,988],{"class":1178},[1109,2068,1181],{"class":1141},[1109,2070,2071],{"class":1133},"] ",[1109,2073,2074],{"class":1141},"},\n",[1109,2076,2078,2081,2083],{"class":1111,"line":2077},15,[1109,2079,2080],{"class":1167},"      member",[1109,2082,1564],{"class":1141},[1109,2084,1155],{"class":1141},[1109,2086,2088,2090,2092,2094,2097,2099],{"class":1111,"line":2087},16,[1109,2089,1877],{"class":1167},[1109,2091,1564],{"class":1141},[1109,2093,1175],{"class":1141},[1109,2095,2096],{"class":1178},"Member",[1109,2098,1181],{"class":1141},[1109,2100,1497],{"class":1141},[1109,2102,2104,2106,2108,2110,2112,2115,2117,2119],{"class":1111,"line":2103},17,[1109,2105,1892],{"class":1167},[1109,2107,1564],{"class":1141},[1109,2109,1840],{"class":1133},[1109,2111,1181],{"class":1141},[1109,2113,2114],{"class":1178},"member-area:view",[1109,2116,1181],{"class":1141},[1109,2118,1849],{"class":1133},[1109,2120,1497],{"class":1141},[1109,2122,2124,2127,2129,2131,2134,2136,2138,2140,2142,2145],{"class":1111,"line":2123},18,[1109,2125,2126],{"class":1167},"        requirements",[1109,2128,1564],{"class":1141},[1109,2130,1196],{"class":1141},[1109,2132,2133],{"class":1167}," subscription",[1109,2135,1564],{"class":1141},[1109,2137,1175],{"class":1141},[1109,2139,1734],{"class":1178},[1109,2141,1181],{"class":1141},[1109,2143,2144],{"class":1141}," },",[1109,2146,2147],{"class":1115}," \u002F\u002F counts only while every requirement holds\n",[1109,2149,2151,2154,2156,2160,2162],{"class":1111,"line":2150},19,[1109,2152,2153],{"class":1167},"        auto",[1109,2155,1564],{"class":1141},[1109,2157,2159],{"class":2158},"sfNiH"," true",[1109,2161,1172],{"class":1141},[1109,2163,2164],{"class":1115}," \u002F\u002F held by everyone who meets the requirements; not assignable\n",[1109,2166,2168,2170,2172,2174,2176,2178,2180,2182],{"class":1111,"line":2167},20,[1109,2169,1984],{"class":1167},[1109,2171,1564],{"class":1141},[1109,2173,1840],{"class":1133},[1109,2175,1181],{"class":1141},[1109,2177,988],{"class":1178},[1109,2179,1181],{"class":1141},[1109,2181,1849],{"class":1133},[1109,2183,1497],{"class":1141},[1109,2185,2187],{"class":1111,"line":2186},21,[1109,2188,1914],{"class":1141},[1109,2190,2192,2195,2197,2199,2201,2203,2205,2208,2210,2212,2214,2216,2218,2220,2223,2225,2227],{"class":1111,"line":2191},22,[1109,2193,2194],{"class":1167},"      user",[1109,2196,1564],{"class":1141},[1109,2198,1196],{"class":1141},[1109,2200,2026],{"class":1167},[1109,2202,1564],{"class":1141},[1109,2204,1175],{"class":1141},[1109,2206,2207],{"class":1178},"User",[1109,2209,1181],{"class":1141},[1109,2211,1172],{"class":1141},[1109,2213,2040],{"class":1167},[1109,2215,1564],{"class":1141},[1109,2217,1840],{"class":1133},[1109,2219,1181],{"class":1141},[1109,2221,2222],{"class":1178},"events:read",[1109,2224,1181],{"class":1141},[1109,2226,2071],{"class":1133},[1109,2228,2074],{"class":1141},[1109,2230,2232],{"class":1111,"line":2231},23,[1109,2233,2234],{"class":1141},"    },\n",[1109,2236,2238],{"class":1111,"line":2237},24,[1109,2239,2240],{"class":1141},"  },\n",[1109,2242,2244,2246],{"class":1111,"line":2243},25,[1109,2245,1296],{"class":1141},[1109,2247,1231],{"class":1133},[616,2249,2250,2259],{},[619,2251,2252],{},[622,2253,2254,2257],{},[625,2255,2256],{},"Field",[625,2258,807],{},[635,2260,2261,2271,2280,2297,2309],{},[622,2262,2263,2268],{},[640,2264,2265],{},[645,2266,2267],{},"grant",[640,2269,2270],{},"Permissions or wildcards the role holds.",[622,2272,2273,2277],{},[640,2274,2275],{},[645,2276,1590],{},[640,2278,2279],{},"Roles whose permissions this role also holds.",[622,2281,2282,2287],{},[640,2283,2284],{},[645,2285,2286],{},"requirements",[640,2288,2289,2292,2293,2296],{},[645,2290,2291],{},"name: value"," pairs. The role only counts while every requirement holds. A requirement is a function a module registers, such as ",[645,2294,2295],{},"subscription"," from Subscriptions.",[622,2298,2299,2303],{},[640,2300,2301],{},[645,2302,1582],{},[640,2304,2305,2306,609],{},"Held by everyone who meets the requirements. Not assignable, and never in ",[645,2307,2308],{},"defaultRoles",[622,2310,2311,2316],{},[640,2312,2313],{},[645,2314,2315],{},"assignable",[640,2317,2318,2319,2321,2322,2324],{},"Whether admins may assign it. Always ",[645,2320,1612],{}," for ",[645,2323,1582],{}," and default roles.",[593,2326,2327,2328,2331,2332,1038,2335,776,2338,664,2341,2344],{},"A user can hold several roles, and holds the union of what they grant. Roles come from config only, so a typo cannot\nbe introduced from a screen and every environment agrees. Only which user holds which role lives in the database\n(",[645,2329,2330],{},"authorization_user_roles","), with a ",[645,2333,2334],{},"source",[645,2336,2337],{},"manual",[645,2339,2340],{},"sso",[645,2342,2343],{},"migrated",". Single sign-on replaces its own rows and\nleaves manual ones alone.",[593,2346,2347,2349,2350,2352],{},[645,2348,2308],{}," is your baseline for every signed-in user. ",[645,2351,1594],{}," is different: it belongs to a permission a\nmodule declares as self-service, and applies even without Authorization.",[593,2354,2355,2358,2359,2362],{},[601,2356,2357],{},"Assigning roles is guarded."," Assigning or removing a role needs ",[645,2360,2361],{},"roles:assign",", and the actor must hold every\npermission the role grants (sensitive ones by name). Editing another user needs the actor to hold every permission\nthat user holds. So a coordinator cannot make themselves an admin, or edit one. These two rules are in the code, not in\npolicies, so no host policy can override them.",[980,2364,2366],{"id":2365},"permissions-and-sensitive-ones","Permissions and sensitive ones",[593,2368,2369,2370,2372,2373,2375,2376,2378,2379,2381,2382,2385,2386,2389,2390,609],{},"A permission is ",[645,2371,824],{},". A held ",[645,2374,1734],{}," matches every permission, and ",[645,2377,1737],{}," every action of one resource,\nwith one exception: ",[601,2380,955],{}," permissions. ",[645,2383,2384],{},":pii"," permissions are sensitive by default, and a module can mark\nothers. A wildcard never covers them, so a role or a token sees other people's personal data only when it was granted\nby name. That is also why ",[645,2387,2388],{},"grant: ['*']"," on an admin role does not include ",[645,2391,2392],{},"forms:pii",[1779,2394,2395,2396,864,2398,2400,2401,2404,2405,864,2407,2410],{},"For now the built-in sensitive permissions, ",[645,2397,951],{},[645,2399,2392],{},", apply to ",[601,2402,2403],{},"MCP tools only",": they gate\npersonal data in MCP tool output. The admin HTTP API of the same data (attendees, submissions) is gated by\n",[645,2406,828],{},[645,2408,2409],{},"forms:manage",", so a role that manages events or forms sees it in the admin pages.",[2412,2413,966],"h4",{"id":2414},"aliases",[593,2416,2417],{},"When a permission is split, the old name becomes an alias of the new ones. Holding the alias holds them all, so\nexisting roles and tokens keep working. It works on the held side only: code that checks the old name fails, because an\nunregistered name is unknown.",[980,2419,209],{"id":1304},[593,2421,2422],{},"A policy refines one permission for one record. It runs after the roles, only when the check has a record.",[1100,2424,2427],{"className":1102,"code":2425,"filename":2426,"language":1104,"meta":1105,"style":1105},"export default definePolicy('events:manage', ({ user, record }) => {\n  \u002F\u002F true or undefined keeps the role's answer, false denies\n  return record.status !== 'archived' && (record.ownerId === user.id || undefined)\n})\n","server\u002Fpolicies\u002Fevents.ts",[645,2428,2429,2466,2471,2523],{"__ignoreMap":1105},[1109,2430,2431,2433,2435,2438,2440,2442,2444,2446,2448,2451,2454,2456,2459,2462,2464],{"class":1111,"line":1112},[1109,2432,1123],{"class":1122},[1109,2434,1126],{"class":1122},[1109,2436,2437],{"class":1129}," definePolicy",[1109,2439,1134],{"class":1133},[1109,2441,1181],{"class":1141},[1109,2443,828],{"class":1178},[1109,2445,1181],{"class":1141},[1109,2447,1172],{"class":1141},[1109,2449,2450],{"class":1141}," ({",[1109,2452,2453],{"class":1145}," user",[1109,2455,1172],{"class":1141},[1109,2457,2458],{"class":1145}," record",[1109,2460,2461],{"class":1141}," })",[1109,2463,1152],{"class":1137},[1109,2465,1155],{"class":1141},[1109,2467,2468],{"class":1111,"line":1119},[1109,2469,2470],{"class":1115},"  \u002F\u002F true or undefined keeps the role's answer, false denies\n",[1109,2472,2473,2476,2478,2480,2483,2486,2488,2491,2493,2496,2498,2501,2503,2506,2509,2511,2513,2515,2518,2521],{"class":1111,"line":1158},[1109,2474,2475],{"class":1122},"  return",[1109,2477,2458],{"class":1133},[1109,2479,609],{"class":1141},[1109,2481,2482],{"class":1133},"status",[1109,2484,2485],{"class":1141}," !==",[1109,2487,1175],{"class":1141},[1109,2489,2490],{"class":1178},"archived",[1109,2492,1181],{"class":1141},[1109,2494,2495],{"class":1141}," &&",[1109,2497,1142],{"class":1167},[1109,2499,2500],{"class":1133},"record",[1109,2502,609],{"class":1141},[1109,2504,2505],{"class":1133},"ownerId",[1109,2507,2508],{"class":1141}," ===",[1109,2510,2453],{"class":1133},[1109,2512,609],{"class":1141},[1109,2514,1251],{"class":1133},[1109,2516,2517],{"class":1141}," ||",[1109,2519,2520],{"class":1141}," undefined",[1109,2522,1231],{"class":1167},[1109,2524,2525,2527],{"class":1111,"line":1190},[1109,2526,1296],{"class":1141},[1109,2528,1231],{"class":1133},[593,2530,2531,2532,776,2534,664,2536,2538,2539,2541,2542,864,2544,2546,2547,2550],{},"A policy returns ",[645,2533,1616],{},[645,2535,1612],{},[645,2537,1619],{},". Any ",[645,2540,1612],{}," denies, and ",[645,2543,1616],{},[645,2545,1619],{}," keep the result, so\npolicies narrow a grant and never create one. An \"owner may edit\" rule is ",[645,2548,2549],{},"record.ownerId === user.id || undefined"," on\ntop of a role grant. Module policies run before host ones, and policy results are never cached.",[593,2552,2553,2556,2557,2560,2561,664,2564,2567],{},[645,2554,2555],{},"defineSharedPolicy"," is the same for rules that must also run in the browser, so a button's ",[645,2558,2559],{},"can()"," agrees with the\nserver. A shared policy is synchronous and imports nothing from ",[645,2562,2563],{},"#imports",[645,2565,2566],{},"@nuxthub\u002Fdb","; the build fails if it does.",[980,2569,718],{"id":2570},"the-before-hook",[593,2572,2573,2575,2576,2579],{},[645,2574,712],{}," is the one place your host app overrides the answer, like Laravel's ",[645,2577,2578],{},"Gate::before",". It runs after\nthe credential checks (steps 3 and 4), so it can never widen a token.",[1100,2581,2584],{"className":1102,"code":2582,"filename":2583,"language":1104,"meta":1105,"style":1105},"export default defineNitroPlugin((nitro) => {\n  nitro.hooks.hook('authorization:before', (ctx) => {\n    \u002F\u002F identify the caller from ctx.user, never from the session on ctx.event: it is empty for bearers\n    if (ctx.permission.startsWith('forms:') && isOnFormsAllowlist(ctx.user)) ctx.allow()\n    if (ctx.user.banned) ctx.deny()\n  })\n})\n","server\u002Fplugins\u002Fauthorization.ts",[645,2585,2586,2608,2644,2649,2706,2734,2741],{"__ignoreMap":1105},[1109,2587,2588,2590,2592,2595,2597,2599,2602,2604,2606],{"class":1111,"line":1112},[1109,2589,1123],{"class":1122},[1109,2591,1126],{"class":1122},[1109,2593,2594],{"class":1129}," defineNitroPlugin",[1109,2596,1134],{"class":1133},[1109,2598,1134],{"class":1141},[1109,2600,2601],{"class":1145},"nitro",[1109,2603,1149],{"class":1141},[1109,2605,1152],{"class":1137},[1109,2607,1155],{"class":1141},[1109,2609,2610,2613,2615,2618,2620,2623,2625,2627,2629,2631,2633,2635,2638,2640,2642],{"class":1111,"line":1119},[1109,2611,2612],{"class":1133},"  nitro",[1109,2614,609],{"class":1141},[1109,2616,2617],{"class":1133},"hooks",[1109,2619,609],{"class":1141},[1109,2621,2622],{"class":1129},"hook",[1109,2624,1134],{"class":1167},[1109,2626,1181],{"class":1141},[1109,2628,712],{"class":1178},[1109,2630,1181],{"class":1141},[1109,2632,1172],{"class":1141},[1109,2634,1142],{"class":1141},[1109,2636,2637],{"class":1145},"ctx",[1109,2639,1149],{"class":1141},[1109,2641,1152],{"class":1137},[1109,2643,1155],{"class":1141},[1109,2645,2646],{"class":1111,"line":1158},[1109,2647,2648],{"class":1115},"    \u002F\u002F identify the caller from ctx.user, never from the session on ctx.event: it is empty for bearers\n",[1109,2650,2651,2654,2656,2658,2660,2663,2665,2668,2670,2672,2675,2677,2679,2682,2685,2687,2689,2691,2693,2696,2698,2700,2703],{"class":1111,"line":1190},[1109,2652,2653],{"class":1122},"    if",[1109,2655,1142],{"class":1167},[1109,2657,2637],{"class":1133},[1109,2659,609],{"class":1141},[1109,2661,2662],{"class":1133},"permission",[1109,2664,609],{"class":1141},[1109,2666,2667],{"class":1129},"startsWith",[1109,2669,1134],{"class":1167},[1109,2671,1181],{"class":1141},[1109,2673,2674],{"class":1178},"forms:",[1109,2676,1181],{"class":1141},[1109,2678,1184],{"class":1167},[1109,2680,2681],{"class":1141},"&&",[1109,2683,2684],{"class":1129}," isOnFormsAllowlist",[1109,2686,1134],{"class":1167},[1109,2688,2637],{"class":1133},[1109,2690,609],{"class":1141},[1109,2692,988],{"class":1133},[1109,2694,2695],{"class":1167},")) ",[1109,2697,2637],{"class":1133},[1109,2699,609],{"class":1141},[1109,2701,2702],{"class":1129},"allow",[1109,2704,2705],{"class":1167},"()\n",[1109,2707,2708,2710,2712,2714,2716,2718,2720,2723,2725,2727,2729,2732],{"class":1111,"line":1234},[1109,2709,2653],{"class":1122},[1109,2711,1142],{"class":1167},[1109,2713,2637],{"class":1133},[1109,2715,609],{"class":1141},[1109,2717,988],{"class":1133},[1109,2719,609],{"class":1141},[1109,2721,2722],{"class":1133},"banned",[1109,2724,1184],{"class":1167},[1109,2726,2637],{"class":1133},[1109,2728,609],{"class":1141},[1109,2730,2731],{"class":1129},"deny",[1109,2733,2705],{"class":1167},[1109,2735,2736,2739],{"class":1111,"line":1259},[1109,2737,2738],{"class":1141},"  }",[1109,2740,1231],{"class":1167},[1109,2742,2743,2745],{"class":1111,"line":1287},[1109,2744,1296],{"class":1141},[1109,2746,1231],{"class":1133},[593,2748,2749,2750,2752,2753,2755,2756,2758],{},"Results are collected across listeners: any ",[645,2751,1568],{}," or a throw denies, otherwise any ",[645,2754,1572],{}," allows. A listener\nthat needs to know what the user could otherwise do must not call ",[645,2757,1097],{}," for the same permission, which would\nfire the hook again.",[593,2760,2761,2762,2764,2765,2768],{},"To map an environment allowlist to roles instead, answer ",[645,2763,1586],{},". It receives ",[645,2766,2767],{},"{ event, user, roles }",":\npush a role to add it. The role still has to pass its requirements.",[980,2770,765],{"id":2771},"sessiononly",[1083,2773,2774,2775,2777,2778,2781],{},"Mark a permission ",[645,2776,758],{}," when it changes ",[601,2779,2780],{},"who the user is or how they sign in",", or moves money:\npassword, email, passkeys, API tokens, the billing portal, role assignment, refunds. A leaked API token must not be\nable to mint more credentials or take over the account.",[593,2783,2784,2786,2787,776,2790,2793,2794,2797],{},[645,2785,765],{}," belongs to the permission definition, never to a call. Money and credential actions get their own\npermissions (",[645,2788,2789],{},"events:refund",[645,2791,2792],{},"users:manage","), so one rule holds wherever the permission is checked: a token can\nnever hold it and it is never offered as a scope. ",[645,2795,2796],{},"requireAuth(event, { sessionOnly: true })"," remains for the few\nroutes that need a signed-in session without any permission.",[980,2799,748],{"id":2800},"scopes",[593,2802,2803],{},"A scope is what a delegated credential may do. It is one of:",[903,2805,2806,2818,2829],{},[906,2807,2808,2809,2811,2812,776,2814,2817],{},"a permission (",[645,2810,863],{},"), or a wildcard (",[645,2813,1734],{},[645,2815,2816],{},"tickets:*",");",[906,2819,2820,2821,2824,2825,2828],{},"a ",[601,2822,2823],{},"named scope"," that expands to permissions: ",[645,2826,2827],{},"{ name: 'tickets', permissions: ['tickets:read', 'tickets:write'] }",";",[906,2830,2831,2832,776,2834,776,2837,2840],{},"an OpenID Connect scope (",[645,2833,871],{},[645,2835,2836],{},"profile",[645,2838,2839],{},"email",") that expands to none.",[593,2842,2843,2844,2846,2847,2850,2851,609],{},"Never a sensitive or ",[645,2845,765],{}," permission inside a named scope. A user may only grant a scope when every\npermission it expands to passes the before hook and their roles (",[645,2848,2849],{},"canGrantScope","). For OAuth there is a further cap per\nrole and a client check for wildcards: see\n",[606,2852,136],{"href":2853},"\u002Fguide\u002Fgoing-further\u002Foauth-application#scopes-consent-and-roles",[593,2855,2856],{},"Scopes only narrow: a token never does more than its owner can, now or when it was issued.",[980,2858,2860],{"id":2859},"where-the-browser-gets-its-answer","Where the browser gets its answer",[593,2862,2863,2866,2867,2870,2871,2873,2874,2877,2878,864,2881,2884],{},[645,2864,2865],{},"GET \u002Fapi\u002Fauth\u002Fme\u002Faccess"," returns ",[645,2868,2869],{},"{ kind, scopes, permissions, roles? }",": the registered permissions the caller\npasses without a record. ",[606,2872,199],{"href":200}," keeps it in ",[645,2875,2876],{},"useState",", refreshes it on\nsign-in and sign-out, and serves ",[645,2879,2880],{},"useAuthorization()",[645,2882,2883],{},"\u003CCan>"," from it, so the interface agrees with the server:",[1100,2886,2890],{"className":2887,"code":2888,"language":2889,"meta":1105,"style":1105},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003CCan permission=\"events:manage\" :record=\"ev\">\u003CUButton label=\"Edit event\" \u002F>\u003C\u002FCan>\n","vue",[645,2891,2892],{"__ignoreMap":1105},[1109,2893,2894,2897,2900,2902,2905,2908,2910,2912,2915,2917,2919,2921,2924,2926,2929,2932,2935,2937],{"class":1111,"line":1112},[1109,2895,2896],{"class":1141},"\u003C",[1109,2898,2899],{"class":1167},"Can",[1109,2901,1722],{"class":1137},[1109,2903,2904],{"class":1141},"=",[1109,2906,2907],{"class":1141},"\"",[1109,2909,828],{"class":1178},[1109,2911,2907],{"class":1141},[1109,2913,2914],{"class":1141}," :",[1109,2916,2500],{"class":1137},[1109,2918,2904],{"class":1141},[1109,2920,2907],{"class":1141},[1109,2922,2923],{"class":1133},"ev",[1109,2925,2907],{"class":1141},[1109,2927,2928],{"class":1141},">",[1109,2930,2931],{"class":1133},"\u003CUButton label=\"Edit event\" \u002F>",[1109,2933,2934],{"class":1141},"\u003C\u002F",[1109,2936,2899],{"class":1167},[1109,2938,2939],{"class":1141},">\n",[1100,2941,2943],{"className":1102,"code":2942,"language":1104,"meta":1105,"style":1105},"\u002F\u002F nuxt.config.ts: auth enforces route rules on the server too, so they fail closed\nrouteRules: { '\u002Fadmin\u002F**': { permission: 'admin:access' } } \u002F\u002F keys: permission, authenticated, guest\n",[645,2944,2945,2950],{"__ignoreMap":1105},[1109,2946,2947],{"class":1111,"line":1112},[1109,2948,2949],{"class":1115},"\u002F\u002F nuxt.config.ts: auth enforces route rules on the server too, so they fail closed\n",[1109,2951,2952,2956,2958,2960,2962,2965,2967,2969,2972,2974,2976,2978,2981,2983,2985,2987],{"class":1111,"line":1119},[1109,2953,2955],{"class":2954},"sBMFI","routeRules",[1109,2957,1564],{"class":1141},[1109,2959,1196],{"class":1141},[1109,2961,1175],{"class":1141},[1109,2963,2964],{"class":1178},"\u002Fadmin\u002F**",[1109,2966,1181],{"class":1141},[1109,2968,1038],{"class":1167},[1109,2970,2971],{"class":1141},"{",[1109,2973,1722],{"class":1167},[1109,2975,1564],{"class":1141},[1109,2977,1175],{"class":1141},[1109,2979,2980],{"class":1178},"admin:access",[1109,2982,1181],{"class":1141},[1109,2984,1202],{"class":1141},[1109,2986,1202],{"class":1141},[1109,2988,2989],{"class":1115}," \u002F\u002F keys: permission, authenticated, guest\n",[1100,2991,2993],{"className":1102,"code":2992,"language":1104,"meta":1105,"style":1105},"\u002F\u002F a page\ndefinePageMeta({ authorize: 'events:manage' })\n",[645,2994,2995,3000],{"__ignoreMap":1105},[1109,2996,2997],{"class":1111,"line":1112},[1109,2998,2999],{"class":1115},"\u002F\u002F a page\n",[1109,3001,3002,3005,3007,3009,3011,3013,3015,3017,3019,3021],{"class":1111,"line":1119},[1109,3003,3004],{"class":1129},"definePageMeta",[1109,3006,1134],{"class":1133},[1109,3008,2971],{"class":1141},[1109,3010,1164],{"class":1167},[1109,3012,1564],{"class":1141},[1109,3014,1175],{"class":1141},[1109,3016,828],{"class":1178},[1109,3018,1181],{"class":1141},[1109,3020,1202],{"class":1141},[1109,3022,1231],{"class":1133},[593,3024,3025,3026,3029,3030,3032],{},"In client mode (a mobile or single-page app) the same composable asks the issuer's ",[645,3027,3028],{},"\u002Fme\u002Faccess"," with the bearer token,\nor decodes the ",[645,3031,1080],{}," claim of the access token when the issuer has no such endpoint. It is for the interface\nonly: the API decides.",[611,3034,788],{"id":3035},"how-modules-contribute",[980,3037,3039],{"id":3038},"declaring-access","Declaring access",[593,3041,3042,3043,1564],{},"A module declares its permissions, named scopes and default roles in one typed file, and registers it from ",[645,3044,3045],{},"setup()",[1100,3047,3050],{"className":1102,"code":3048,"filename":3049,"language":1104,"meta":1105,"style":1105},"import { defineAccess } from '@frigear-nu\u002Fauth\u002Faccess'\n\nexport const reportsAccess = defineAccess('reports', {\n  permissions: {\n    read: { label: 'Read reports', implicit: true }, \u002F\u002F reports:read, every signed-in user\n    export: { label: 'Export reports', grantTo: ['admin'] }, \u002F\u002F given to a role by name\n    pii: { label: 'See personal data in reports' }, \u002F\u002F sensitive by default\n  },\n  scopes: { reports: { label: 'Your reports', permissions: ['reports:read'] } },\n  roles: { reporter: { label: 'Reporter', grant: ['reports:*'] } },\n})\n","src\u002Fruntime\u002Faccess.ts",[645,3051,3052,3075,3081,3108,3117,3151,3195,3220,3224,3274,3322],{"__ignoreMap":1105},[1109,3053,3054,3057,3059,3062,3064,3067,3069,3072],{"class":1111,"line":1112},[1109,3055,3056],{"class":1122},"import",[1109,3058,1196],{"class":1141},[1109,3060,3061],{"class":1133}," defineAccess",[1109,3063,1202],{"class":1141},[1109,3065,3066],{"class":1122}," from",[1109,3068,1175],{"class":1141},[1109,3070,3071],{"class":1178},"@frigear-nu\u002Fauth\u002Faccess",[1109,3073,3074],{"class":1141},"'\n",[1109,3076,3077],{"class":1111,"line":1119},[1109,3078,3080],{"emptyLinePlaceholder":3079},true,"\n",[1109,3082,3083,3085,3088,3091,3093,3095,3097,3099,3102,3104,3106],{"class":1111,"line":1158},[1109,3084,1123],{"class":1122},[1109,3086,3087],{"class":1137}," const",[1109,3089,3090],{"class":1133}," reportsAccess ",[1109,3092,2904],{"class":1141},[1109,3094,3061],{"class":1129},[1109,3096,1134],{"class":1133},[1109,3098,1181],{"class":1141},[1109,3100,3101],{"class":1178},"reports",[1109,3103,1181],{"class":1141},[1109,3105,1172],{"class":1141},[1109,3107,1155],{"class":1141},[1109,3109,3110,3113,3115],{"class":1111,"line":1190},[1109,3111,3112],{"class":1167},"  permissions",[1109,3114,1564],{"class":1141},[1109,3116,1155],{"class":1141},[1109,3118,3119,3122,3124,3126,3128,3130,3132,3135,3137,3139,3142,3144,3146,3148],{"class":1111,"line":1234},[1109,3120,3121],{"class":1167},"    read",[1109,3123,1564],{"class":1141},[1109,3125,1196],{"class":1141},[1109,3127,2026],{"class":1167},[1109,3129,1564],{"class":1141},[1109,3131,1175],{"class":1141},[1109,3133,3134],{"class":1178},"Read reports",[1109,3136,1181],{"class":1141},[1109,3138,1172],{"class":1141},[1109,3140,3141],{"class":1167}," implicit",[1109,3143,1564],{"class":1141},[1109,3145,2159],{"class":2158},[1109,3147,2144],{"class":1141},[1109,3149,3150],{"class":1115}," \u002F\u002F reports:read, every signed-in user\n",[1109,3152,3153,3156,3158,3160,3162,3164,3166,3169,3171,3173,3176,3178,3180,3182,3185,3187,3189,3192],{"class":1111,"line":1259},[1109,3154,3155],{"class":1167},"    export",[1109,3157,1564],{"class":1141},[1109,3159,1196],{"class":1141},[1109,3161,2026],{"class":1167},[1109,3163,1564],{"class":1141},[1109,3165,1175],{"class":1141},[1109,3167,3168],{"class":1178},"Export reports",[1109,3170,1181],{"class":1141},[1109,3172,1172],{"class":1141},[1109,3174,3175],{"class":1167}," grantTo",[1109,3177,1564],{"class":1141},[1109,3179,1840],{"class":1133},[1109,3181,1181],{"class":1141},[1109,3183,3184],{"class":1178},"admin",[1109,3186,1181],{"class":1141},[1109,3188,2071],{"class":1133},[1109,3190,3191],{"class":1141},"},",[1109,3193,3194],{"class":1115}," \u002F\u002F given to a role by name\n",[1109,3196,3197,3200,3202,3204,3206,3208,3210,3213,3215,3217],{"class":1111,"line":1287},[1109,3198,3199],{"class":1167},"    pii",[1109,3201,1564],{"class":1141},[1109,3203,1196],{"class":1141},[1109,3205,2026],{"class":1167},[1109,3207,1564],{"class":1141},[1109,3209,1175],{"class":1141},[1109,3211,3212],{"class":1178},"See personal data in reports",[1109,3214,1181],{"class":1141},[1109,3216,2144],{"class":1141},[1109,3218,3219],{"class":1115}," \u002F\u002F sensitive by default\n",[1109,3221,3222],{"class":1111,"line":1293},[1109,3223,2240],{"class":1141},[1109,3225,3226,3229,3231,3233,3236,3238,3240,3242,3244,3246,3249,3251,3253,3256,3258,3260,3262,3265,3267,3269,3271],{"class":1111,"line":1917},[1109,3227,3228],{"class":1167},"  scopes",[1109,3230,1564],{"class":1141},[1109,3232,1196],{"class":1141},[1109,3234,3235],{"class":1167}," reports",[1109,3237,1564],{"class":1141},[1109,3239,1196],{"class":1141},[1109,3241,2026],{"class":1167},[1109,3243,1564],{"class":1141},[1109,3245,1175],{"class":1141},[1109,3247,3248],{"class":1178},"Your reports",[1109,3250,1181],{"class":1141},[1109,3252,1172],{"class":1141},[1109,3254,3255],{"class":1167}," permissions",[1109,3257,1564],{"class":1141},[1109,3259,1840],{"class":1133},[1109,3261,1181],{"class":1141},[1109,3263,3264],{"class":1178},"reports:read",[1109,3266,1181],{"class":1141},[1109,3268,2071],{"class":1133},[1109,3270,1296],{"class":1141},[1109,3272,3273],{"class":1141}," },\n",[1109,3275,3276,3279,3281,3283,3286,3288,3290,3292,3294,3296,3299,3301,3303,3305,3307,3309,3311,3314,3316,3318,3320],{"class":1111,"line":1927},[1109,3277,3278],{"class":1167},"  roles",[1109,3280,1564],{"class":1141},[1109,3282,1196],{"class":1141},[1109,3284,3285],{"class":1167}," reporter",[1109,3287,1564],{"class":1141},[1109,3289,1196],{"class":1141},[1109,3291,2026],{"class":1167},[1109,3293,1564],{"class":1141},[1109,3295,1175],{"class":1141},[1109,3297,3298],{"class":1178},"Reporter",[1109,3300,1181],{"class":1141},[1109,3302,1172],{"class":1141},[1109,3304,2040],{"class":1167},[1109,3306,1564],{"class":1141},[1109,3308,1840],{"class":1133},[1109,3310,1181],{"class":1141},[1109,3312,3313],{"class":1178},"reports:*",[1109,3315,1181],{"class":1141},[1109,3317,2071],{"class":1133},[1109,3319,1296],{"class":1141},[1109,3321,3273],{"class":1141},[1109,3323,3324,3326],{"class":1111,"line":1943},[1109,3325,1296],{"class":1141},[1109,3327,1231],{"class":1133},[1100,3329,3332],{"className":1102,"code":3330,"filename":3331,"language":1104,"meta":1105,"style":1105},"import { addPolicies, addRoleRequirement, registerAccess } from '@frigear-nu\u002Fauth\u002Futils'\nimport { reportsAccess } from '.\u002Fruntime\u002Faccess'\n\nregisterAccess(nuxt, reportsAccess)\naddRoleRequirement(nuxt, { name: 'department', path: resolver.resolve('.\u002Fruntime\u002Fserver\u002Frequirements\u002Fdepartment') })\naddPolicies(nuxt, { path: resolver.resolve('.\u002Fruntime\u002Fserver\u002Fpolicies\u002Freports') })\n","src\u002Fmodule.ts",[645,3333,3334,3364,3384,3388,3401,3454],{"__ignoreMap":1105},[1109,3335,3336,3338,3340,3343,3345,3348,3350,3353,3355,3357,3359,3362],{"class":1111,"line":1112},[1109,3337,3056],{"class":1122},[1109,3339,1196],{"class":1141},[1109,3341,3342],{"class":1133}," addPolicies",[1109,3344,1172],{"class":1141},[1109,3346,3347],{"class":1133}," addRoleRequirement",[1109,3349,1172],{"class":1141},[1109,3351,3352],{"class":1133}," registerAccess",[1109,3354,1202],{"class":1141},[1109,3356,3066],{"class":1122},[1109,3358,1175],{"class":1141},[1109,3360,3361],{"class":1178},"@frigear-nu\u002Fauth\u002Futils",[1109,3363,3074],{"class":1141},[1109,3365,3366,3368,3370,3373,3375,3377,3379,3382],{"class":1111,"line":1119},[1109,3367,3056],{"class":1122},[1109,3369,1196],{"class":1141},[1109,3371,3372],{"class":1133}," reportsAccess",[1109,3374,1202],{"class":1141},[1109,3376,3066],{"class":1122},[1109,3378,1175],{"class":1141},[1109,3380,3381],{"class":1178},".\u002Fruntime\u002Faccess",[1109,3383,3074],{"class":1141},[1109,3385,3386],{"class":1111,"line":1158},[1109,3387,3080],{"emptyLinePlaceholder":3079},[1109,3389,3390,3393,3396,3398],{"class":1111,"line":1190},[1109,3391,3392],{"class":1129},"registerAccess",[1109,3394,3395],{"class":1133},"(nuxt",[1109,3397,1172],{"class":1141},[1109,3399,3400],{"class":1133}," reportsAccess)\n",[1109,3402,3403,3406,3408,3410,3412,3415,3417,3419,3422,3424,3426,3429,3431,3434,3436,3439,3441,3443,3446,3448,3450,3452],{"class":1111,"line":1234},[1109,3404,3405],{"class":1129},"addRoleRequirement",[1109,3407,3395],{"class":1133},[1109,3409,1172],{"class":1141},[1109,3411,1196],{"class":1141},[1109,3413,3414],{"class":1167}," name",[1109,3416,1564],{"class":1141},[1109,3418,1175],{"class":1141},[1109,3420,3421],{"class":1178},"department",[1109,3423,1181],{"class":1141},[1109,3425,1172],{"class":1141},[1109,3427,3428],{"class":1167}," path",[1109,3430,1564],{"class":1141},[1109,3432,3433],{"class":1133}," resolver",[1109,3435,609],{"class":1141},[1109,3437,3438],{"class":1129},"resolve",[1109,3440,1134],{"class":1133},[1109,3442,1181],{"class":1141},[1109,3444,3445],{"class":1178},".\u002Fruntime\u002Fserver\u002Frequirements\u002Fdepartment",[1109,3447,1181],{"class":1141},[1109,3449,1184],{"class":1133},[1109,3451,1296],{"class":1141},[1109,3453,1231],{"class":1133},[1109,3455,3456,3459,3461,3463,3465,3467,3469,3471,3473,3475,3477,3479,3482,3484,3486,3488],{"class":1111,"line":1259},[1109,3457,3458],{"class":1129},"addPolicies",[1109,3460,3395],{"class":1133},[1109,3462,1172],{"class":1141},[1109,3464,1196],{"class":1141},[1109,3466,3428],{"class":1167},[1109,3468,1564],{"class":1141},[1109,3470,3433],{"class":1133},[1109,3472,609],{"class":1141},[1109,3474,3438],{"class":1129},[1109,3476,1134],{"class":1133},[1109,3478,1181],{"class":1141},[1109,3480,3481],{"class":1178},".\u002Fruntime\u002Fserver\u002Fpolicies\u002Freports",[1109,3483,1181],{"class":1141},[1109,3485,1184],{"class":1133},[1109,3487,1296],{"class":1141},[1109,3489,1231],{"class":1133},[593,3491,3492,3493,3495,3496,3499,3500,860,3503,3506,3507,3509,3510,776,3512,3515,3516,3519,3520,3522,3523,609],{},"The keys become the names (",[645,3494,3264],{},") and the i18n keys (",[645,3497,3498],{},"reports.permissions.read","), and the result is typed:\n",[645,3501,3502],{},"reportsAccess.permissions.export",[645,3504,3505],{},"'reports:export'",", for routes and tests. Auth also generates the ",[645,3508,818],{},"\ntype from everything registered, so a misspelled permission anywhere in the app (",[645,3511,1097],{},[645,3513,3514],{},"can",", route rules,\n",[645,3517,3518],{},"defineRoles"," in ",[645,3521,1805],{},") fails typecheck. The full guide is\n",[606,3524,3525],{"href":542},"Access control for module authors",[593,3527,3528,3530,3531,3533],{},[645,3529,3392],{}," calls these build APIs, which every module's ",[645,3532,3045],{}," can also call directly:",[616,3535,3536,3546],{},[619,3537,3538],{},[622,3539,3540,3543],{},[625,3541,3542],{},"API",[625,3544,3545],{},"What it does",[635,3547,3548,3570,3590,3599,3611],{},[622,3549,3550,3554],{},[640,3551,3552],{},[645,3553,775],{},[640,3555,3556,3557,3560,3561,864,3563,3565,3566,3569],{},"Registers permissions (full ",[645,3558,3559],{},"name","s). The same name from two sources must agree on ",[645,3562,955],{},[645,3564,765],{},", or the build fails. ",[645,3567,3568],{},"grantTo"," puts a permission into a role by name (a role that does not exist is skipped with a note); roles that inherit it get it too.",[622,3571,3572,3576],{},[640,3573,3574],{},[645,3575,779],{},[640,3577,3578,3579,3581,3582,864,3584,3586,3587,3589],{},"Ships a default role, such as ",[645,3580,1993],{}," from Events. Two modules defining one role merge ",[645,3583,2267],{},[645,3585,1590],{},"; conflicting ",[645,3588,2286],{}," fail the build.",[622,3591,3592,3596],{},[640,3593,3594],{},[645,3595,782],{},[640,3597,3598],{},"Registers a named scope. It works for API tokens and OAuth alike, so a module needs no OAuth Server dependency.",[622,3600,3601,3605],{},[640,3602,3603],{},[645,3604,3405],{},[640,3606,3607,3608,609],{},"Registers a requirement a role can name. The file's default export is ",[645,3609,3610],{},"({ event, user, value }) => boolean",[622,3612,3613,3617],{},[640,3614,3615],{},[645,3616,3458],{},[640,3618,3619,3620,864,3622,3624],{},"Registers a file of ",[645,3621,697],{},[645,3623,2555],{}," exports.",[980,3626,3628],{"id":3627},"how-host-config-merges","How host config merges",[593,3630,3631,3632,3635],{},"When your ",[645,3633,3634],{},"authorization.roles.\u003Cname>"," matches a role a module shipped, it merges into it:",[903,3637,3638,3648,3655,3661],{},[906,3639,3640,3641,864,3644,3647],{},"your ",[645,3642,3643],{},"label",[645,3645,3646],{},"description"," win;",[906,3649,3650,864,3652,3654],{},[645,3651,2267],{},[645,3653,1590],{}," are the union of both;",[906,3656,3640,3657,864,3659,3647],{},[645,3658,2286],{},[645,3660,1582],{},[906,3662,3663,3664,3667],{},"with ",[645,3665,3666],{},"replace: true"," you discard the module's definition entirely, including permissions other modules granted to\nthat role by name.",[593,3669,3670,3671,3674],{},"Permissions your host app adds under ",[645,3672,3673],{},"authentication.permissions"," register like a module's.",[593,3676,3677,3678,3680,3681,3683],{},"The build fails on a grant that names no registered permission (a wildcard must match at least one), an unknown\nrequirement, an inheritance cycle, a conflicting redefinition, an ",[645,3679,1582],{}," role in ",[645,3682,2308],{},", and an unknown\npermission in a route rule or page meta.",[980,3685,3687],{"id":3686},"provenance","Provenance",[593,3689,3690,3691,3693,3694,3697],{},"The compiled role remembers where each permission came from: the host, a module, a ",[645,3692,3568],{}," of another module, or\ninheritance from a parent role. The roles screen in the admin area shows it, so \"why does this role hold this?\" has a\ndirect answer. A module can read the whole compiled model from the ",[645,3695,3696],{},"auth:access-compiled"," build hook.",[611,3699,3701],{"id":3700},"common-mistakes","Common mistakes",[3703,3704,3705,3708,3709,3711],"warning",{},[601,3706,3707],{},"Answering 404 before 401 or 403."," A route that loads the record first tells a visitor which ids exist. Call\n",[645,3710,1097],{}," before you load anything, and again with the record afterwards.",[3703,3713,3714,3717,3718,3721,3722,3725],{},[601,3715,3716],{},"Reading the session in a before hook or policy."," Identify the caller from ",[645,3719,3720],{},"ctx.user",", never from the session on\n",[645,3723,3724],{},"ctx.event",". The session is empty for bearer requests.",[3703,3727,3728,3731,3732,3734,3735,3737],{},[601,3729,3730],{},"Checking a name nobody registered."," An unknown permission is always ",[645,3733,1488],{},", also for administrators.\nRegister it with ",[645,3736,775],{},", and let the build catch the typo in route rules and page meta.",[3703,3739,3740,1529,3743,3745,3746,3748],{},[601,3741,3742],{},"Hiding a button and calling it done.",[645,3744,2883],{},", menu filtering and route rules shape the interface. The route behind\nthem calls ",[645,3747,1097],{}," itself.",[3703,3750,3751,3754,3755,664,3757,3759,3760,3763,3764,864,3767,3770],{},[601,3752,3753],{},"A missing module dependency."," A module whose routes call ",[645,3756,1097],{},[645,3758,1500],{}," without listing\n",[645,3761,3762],{},"@frigear-nu\u002Fauth"," in its ",[645,3765,3766],{},"moduleDependencies",[645,3768,3769],{},"package.json"," breaks in any app that does not install Auth some\nother way.",[611,3772,3774],{"id":3773},"next-steps","Next steps",[903,3776,3777,3782,3787,3792,3798],{},[906,3778,3779,3781],{},[606,3780,482],{"href":483}," – apply these checks to a route of your own.",[906,3783,3784,3786],{},[606,3785,121],{"href":122}," – follow the handler order and error shapes.",[906,3788,3789,3791],{},[606,3790,199],{"href":200}," – roles, requirements, the client composable and the roles admin.",[906,3793,3794,3797],{},[606,3795,3796],{"href":542},"Access control in a module"," – register permissions, roles and policies\nfrom your own module.",[906,3799,3800,3802,3803,3806],{},[606,3801,151],{"href":152}," – move an app from abilities, staff\nguards and ",[645,3804,3805],{},"\u003Cmodule>:authorize"," hooks.",[3808,3809,3810],"style",{},"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 .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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":1105,"searchDepth":1119,"depth":1119,"links":3812},[3813,3814,3815,3828,3833,3834],{"id":613,"depth":1119,"text":614},{"id":791,"depth":1119,"text":792},{"id":969,"depth":1119,"text":970,"children":3816},[3817,3818,3819,3820,3821,3822,3823,3824,3825,3826,3827],{"id":982,"depth":1158,"text":673},{"id":1092,"depth":1158,"text":653},{"id":1510,"depth":1158,"text":1511},{"id":1634,"depth":1158,"text":1635},{"id":1795,"depth":1158,"text":734},{"id":2365,"depth":1158,"text":2366},{"id":1304,"depth":1158,"text":209},{"id":2570,"depth":1158,"text":718},{"id":2771,"depth":1158,"text":765},{"id":2800,"depth":1158,"text":748},{"id":2859,"depth":1158,"text":2860},{"id":3035,"depth":1119,"text":788,"children":3829},[3830,3831,3832],{"id":3038,"depth":1158,"text":3039},{"id":3627,"depth":1158,"text":3628},{"id":3686,"depth":1158,"text":3687},{"id":3700,"depth":1119,"text":3701},{"id":3773,"depth":1119,"text":3774},"How a server route learns who is calling and decides what they may do, with one vocabulary of permissions, roles, scopes, policies and a before hook, and what fails closed.","md",null,{},{"icon":70},{"title":67,"description":3835},"O6NKgp_-n1qQ4B5NXCD_p_GNFQJI_9N2lCAthk1SKR4",[3843,3845],{"title":62,"path":63,"stem":64,"description":3844,"icon":65,"children":-1},"Where new code goes, in your app, a module or a package, and why dependencies between modules point one way.",{"title":72,"path":73,"stem":74,"description":3846,"icon":75,"children":-1},"How a module adds menu items, dashboard widgets, tabs, admin pages, permissions, roles, scopes and MCP tools to other modules without importing them.",1794503150713]