[{"data":1,"prerenderedAt":1677},["ShallowReactive",2],{"navigation_docs_en":3,"-en-security-rbac":342,"-en-security-rbac-surround":1672},[4,45,61,96,132,166,196,217,241,265],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":44},"Getting Started","i-lucide-rocket","\u002Fen\u002Fgetting-started","en\u002F1.getting-started",[10,15,20,24,29,34,39],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fen\u002Fgetting-started\u002Fintroduction","en\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Coming from Laravel","\u002Fen\u002Fgetting-started\u002Fcoming-from-laravel","en\u002F1.getting-started\u002F2.coming-from-laravel","i-lucide-arrow-right-left",{"title":21,"path":22,"stem":23,"icon":19},"Coming from Symfony","\u002Fen\u002Fgetting-started\u002Fcoming-from-symfony","en\u002F1.getting-started\u002F3.coming-from-symfony",{"title":25,"path":26,"stem":27,"icon":28},"Installation","\u002Fen\u002Fgetting-started\u002Finstallation","en\u002F1.getting-started\u002F4.installation","i-lucide-download",{"title":30,"path":31,"stem":32,"icon":33},"Your first module","\u002Fen\u002Fgetting-started\u002Ffirst-module","en\u002F1.getting-started\u002F5.first-module","i-lucide-package-plus",{"title":35,"path":36,"stem":37,"icon":38},"Project structure","\u002Fen\u002Fgetting-started\u002Fproject-structure","en\u002F1.getting-started\u002F6.project-structure","i-lucide-folder-tree",{"title":40,"path":41,"stem":42,"icon":43},"The shop sample application","\u002Fen\u002Fgetting-started\u002Fsample-app","en\u002F1.getting-started\u002F7.sample-app","i-lucide-shopping-cart",false,{"title":46,"icon":47,"path":48,"stem":49,"children":50,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F10.reference",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F10.reference\u002F1.configuration","i-lucide-settings",{"title":57,"path":58,"stem":59,"icon":60},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F10.reference\u002F2.dependencies","i-lucide-package",{"title":62,"icon":63,"path":64,"stem":65,"children":66,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[67,72,77,82,86,91],{"title":68,"path":69,"stem":70,"icon":71},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture","i-lucide-layers",{"title":73,"path":74,"stem":75,"icon":76},"Application lifecycle","\u002Fen\u002Fconcepts\u002Fapplication-lifecycle","en\u002F2.concepts\u002F2.application-lifecycle","i-lucide-power",{"title":78,"path":79,"stem":80,"icon":81},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F3.error-model","i-lucide-shield-alert",{"title":52,"path":83,"stem":84,"icon":85},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F4.configuration","i-lucide-settings-2",{"title":87,"path":88,"stem":89,"icon":90},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F5.codegen-pipeline","i-lucide-file-json",{"title":92,"path":93,"stem":94,"icon":95},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F6.design-patterns","i-lucide-puzzle",{"title":97,"icon":98,"path":99,"stem":100,"children":101,"page":44},"HTTP & Routing","i-lucide-globe","\u002Fen\u002Fhttp","en\u002F3.http",[102,107,112,117,122,127],{"title":103,"path":104,"stem":105,"icon":106},"The server core","\u002Fen\u002Fhttp\u002Fserver","en\u002F3.http\u002F1.server","i-lucide-server",{"title":108,"path":109,"stem":110,"icon":111},"The Fiber engine","\u002Fen\u002Fhttp\u002Ffiber","en\u002F3.http\u002F2.fiber","i-lucide-zap",{"title":113,"path":114,"stem":115,"icon":116},"The chi engine","\u002Fen\u002Fhttp\u002Fchi","en\u002F3.http\u002F3.chi","i-lucide-route",{"title":118,"path":119,"stem":120,"icon":121},"Error responses","\u002Fen\u002Fhttp\u002Ferror-responses","en\u002F3.http\u002F4.error-responses","i-lucide-octagon-alert",{"title":123,"path":124,"stem":125,"icon":126},"Validation","\u002Fen\u002Fhttp\u002Fvalidation","en\u002F3.http\u002F5.validation","i-lucide-badge-check",{"title":128,"path":129,"stem":130,"icon":131},"Pagination","\u002Fen\u002Fhttp\u002Fpagination","en\u002F3.http\u002F6.pagination","i-lucide-list-ordered",{"title":133,"icon":134,"path":135,"stem":136,"children":137,"page":44},"Database","i-lucide-database","\u002Fen\u002Fdatabase","en\u002F4.database",[138,142,147,151,156,161],{"title":139,"path":140,"stem":141,"icon":134},"Overview","\u002Fen\u002Fdatabase\u002Foverview","en\u002F4.database\u002F1.overview",{"title":143,"path":144,"stem":145,"icon":146},"pgx","\u002Fen\u002Fdatabase\u002Fpgx","en\u002F4.database\u002F2.pgx","i-lucide-plug",{"title":148,"path":149,"stem":150,"icon":71},"bun","\u002Fen\u002Fdatabase\u002Fbun","en\u002F4.database\u002F3.bun",{"title":152,"path":153,"stem":154,"icon":155},"Transactions","\u002Fen\u002Fdatabase\u002Ftransactions","en\u002F4.database\u002F4.transactions","i-lucide-git-merge",{"title":157,"path":158,"stem":159,"icon":160},"Migrations","\u002Fen\u002Fdatabase\u002Fmigrations","en\u002F4.database\u002F5.migrations","i-lucide-file-stack",{"title":162,"path":163,"stem":164,"icon":165},"Seeders","\u002Fen\u002Fdatabase\u002Fseeding","en\u002F4.database\u002F6.seeding","i-lucide-sprout",{"title":167,"icon":168,"path":169,"stem":170,"children":171,"page":44},"Async & Background","i-lucide-workflow","\u002Fen\u002Fasync","en\u002F5.async",[172,176,181,186,191],{"title":173,"path":174,"stem":175,"icon":131},"Queue","\u002Fen\u002Fasync\u002Fqueue","en\u002F5.async\u002F1.queue",{"title":177,"path":178,"stem":179,"icon":180},"Events","\u002Fen\u002Fasync\u002Fevents","en\u002F5.async\u002F2.events","i-lucide-radio",{"title":182,"path":183,"stem":184,"icon":185},"Outbox","\u002Fen\u002Fasync\u002Foutbox","en\u002F5.async\u002F3.outbox","i-lucide-inbox",{"title":187,"path":188,"stem":189,"icon":190},"Scheduling","\u002Fen\u002Fasync\u002Fscheduler","en\u002F5.async\u002F4.scheduler","i-lucide-calendar-clock",{"title":192,"path":193,"stem":194,"icon":195},"Worker","\u002Fen\u002Fasync\u002Fworker","en\u002F5.async\u002F5.worker","i-lucide-cog",{"title":197,"icon":198,"path":199,"stem":200,"children":201,"page":44},"Security","i-lucide-shield-check","\u002Fen\u002Fsecurity","en\u002F6.security",[202,207,212],{"title":203,"path":204,"stem":205,"icon":206},"Authentication","\u002Fen\u002Fsecurity\u002Fauthentication","en\u002F6.security\u002F1.authentication","i-lucide-key-round",{"title":208,"path":209,"stem":210,"icon":211},"RBAC","\u002Fen\u002Fsecurity\u002Frbac","en\u002F6.security\u002F2.rbac","i-lucide-users",{"title":213,"path":214,"stem":215,"icon":216},"Policies","\u002Fen\u002Fsecurity\u002Fpolicies","en\u002F6.security\u002F3.policies","i-lucide-gavel",{"title":218,"icon":219,"path":220,"stem":221,"children":222,"page":44},"Observability","i-lucide-activity","\u002Fen\u002Fobservability","en\u002F7.observability",[223,226,231,236],{"title":139,"path":224,"stem":225,"icon":219},"\u002Fen\u002Fobservability\u002Foverview","en\u002F7.observability\u002F1.overview",{"title":227,"path":228,"stem":229,"icon":230},"Logging","\u002Fen\u002Fobservability\u002Flogging","en\u002F7.observability\u002F2.logging","i-lucide-scroll-text",{"title":232,"path":233,"stem":234,"icon":235},"OpenTelemetry","\u002Fen\u002Fobservability\u002Fopentelemetry","en\u002F7.observability\u002F3.opentelemetry","i-lucide-radar",{"title":237,"path":238,"stem":239,"icon":240},"Sentry","\u002Fen\u002Fobservability\u002Fsentry","en\u002F7.observability\u002F4.sentry","i-lucide-bug",{"title":242,"icon":243,"path":244,"stem":245,"children":246,"page":44},"Storage & Mail","i-lucide-hard-drive","\u002Fen\u002Fstorage-mail","en\u002F8.storage-mail",[247,251,256,261],{"title":248,"path":249,"stem":250,"icon":243},"Storage","\u002Fen\u002Fstorage-mail\u002Fstorage","en\u002F8.storage-mail\u002F1.storage",{"title":252,"path":253,"stem":254,"icon":255},"Email","\u002Fen\u002Fstorage-mail\u002Fmail","en\u002F8.storage-mail\u002F2.mail","i-lucide-mail",{"title":257,"path":258,"stem":259,"icon":260},"Notifications","\u002Fen\u002Fstorage-mail\u002Fnotifications","en\u002F8.storage-mail\u002F3.notifications","i-lucide-bell",{"title":262,"path":263,"stem":264,"icon":180},"Realtime","\u002Fen\u002Fstorage-mail\u002Frealtime","en\u002F8.storage-mail\u002F4.realtime",{"title":266,"icon":267,"path":268,"stem":269,"children":270,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F9.cli",[271,274,279,284,289,294,299,303,308,313,318,323,328,333,337],{"title":139,"path":272,"stem":273,"icon":267},"\u002Fen\u002Fcli\u002Foverview","en\u002F9.cli\u002F1.overview",{"title":275,"path":276,"stem":277,"icon":278},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F9.cli\u002F10.add-db","i-lucide-database-zap",{"title":280,"path":281,"stem":282,"icon":283},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F9.cli\u002F11.add-compose","i-lucide-container",{"title":285,"path":286,"stem":287,"icon":288},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F9.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":290,"path":291,"stem":292,"icon":293},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F9.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":295,"path":296,"stem":297,"icon":298},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F9.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":300,"path":301,"stem":302,"icon":165},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F9.cli\u002F15.add-seeder",{"title":304,"path":305,"stem":306,"icon":307},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F9.cli\u002F2.new-project","i-lucide-folder-plus",{"title":309,"path":310,"stem":311,"icon":312},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F9.cli\u002F3.new-module","i-lucide-blocks",{"title":314,"path":315,"stem":316,"icon":317},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F9.cli\u002F4.add-surface","i-lucide-layers-2",{"title":319,"path":320,"stem":321,"icon":322},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F9.cli\u002F5.add-core","i-lucide-box",{"title":324,"path":325,"stem":326,"icon":327},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F9.cli\u002F6.add-handler","i-lucide-webhook",{"title":329,"path":330,"stem":331,"icon":332},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F9.cli\u002F7.new-migration","i-lucide-file-plus",{"title":334,"path":335,"stem":336,"icon":195},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F9.cli\u002F8.worker-generators",{"title":338,"path":339,"stem":340,"icon":341},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F9.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"id":343,"title":208,"body":344,"description":1665,"extension":1666,"links":1667,"meta":1668,"navigation":1669,"path":209,"seo":1670,"stem":210,"__hash__":1671},"docs_en\u002Fen\u002F6.security\u002F2.rbac.md",{"type":345,"value":346,"toc":1657},"minimark",[347,377,403,421,426,431,521,528,596,611,663,667,676,733,755,758,944,959,963,978,1383,1389,1447,1451,1478,1485,1489,1512,1605,1614,1618,1653],[348,349,354],"pre",{"className":350,"code":351,"language":352,"meta":353,"style":353},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import \"github.com\u002Fgp-system\u002Fgpsystem\u002Frbac\"\n","go","",[355,356,357],"code",{"__ignoreMap":353},[358,359,362,366,370,374],"span",{"class":360,"line":361},"line",1,[358,363,365],{"class":364},"s7zQu","import",[358,367,369],{"class":368},"sMK4o"," \"",[358,371,373],{"class":372},"sBMFI","github.com\u002Fgp-system\u002Fgpsystem\u002Frbac",[358,375,376],{"class":368},"\"\n",[378,379,380,381,384,385,391,392,396,397,399,400,402],"p",{},"The ",[355,382,383],{},"rbac"," package provides role and permission checks over an authenticated ",[386,387,388],"strong",{},[355,389,390],{},"Identity"," carried in the request context. It deliberately takes no position on where roles come from: the ",[393,394,395],"a",{"href":204},"auth middleware"," (or anything else) populates the ",[355,398,390],{},", and ",[355,401,383],{}," only checks: it assumes no database schema and no user table.",[378,404,405,406,409,410,413,414,409,417,420],{},"The package covers two levels: ",[355,407,408],{},"id.HasRole(\"admin\")"," \u002F ",[355,411,412],{},"id.HasPermission(\"orders.view\")"," for service-layer checks called directly from code, and ",[355,415,416],{},"rbac.RequireRole(\"admin\")",[355,418,419],{},"rbac.RequirePermission(\"orders.view\")"," for route-level middleware protection.",[422,423,425],"h2",{"id":424},"the-identity","The Identity",[378,427,380,428,430],{},[355,429,390],{}," is the caller as authorization decisions and business logic see it: the contract between authentication and authorization.",[348,432,434],{"className":350,"code":433,"language":352,"meta":353,"style":353},"type Identity struct {\n    Subject     string         \u002F\u002F the user's identifier (the JWT sub claim)\n    Username    string\n    Roles       []string\n    Permissions []string\n    Extra       map[string]any \u002F\u002F claim data beyond the standard fields (e.g. tenant)\n}\n",[355,435,436,450,465,474,485,495,515],{"__ignoreMap":353},[358,437,438,441,444,447],{"class":360,"line":361},[358,439,440],{"class":368},"type",[358,442,443],{"class":372}," Identity",[358,445,446],{"class":368}," struct",[358,448,449],{"class":368}," {\n",[358,451,453,457,461],{"class":360,"line":452},2,[358,454,456],{"class":455},"sTEyZ","    Subject     ",[358,458,460],{"class":459},"spNyl","string",[358,462,464],{"class":463},"sHwdD","         \u002F\u002F the user's identifier (the JWT sub claim)\n",[358,466,468,471],{"class":360,"line":467},3,[358,469,470],{"class":455},"    Username    ",[358,472,473],{"class":459},"string\n",[358,475,477,480,483],{"class":360,"line":476},4,[358,478,479],{"class":455},"    Roles       ",[358,481,482],{"class":368},"[]",[358,484,473],{"class":459},[358,486,488,491,493],{"class":360,"line":487},5,[358,489,490],{"class":455},"    Permissions ",[358,492,482],{"class":368},[358,494,473],{"class":459},[358,496,498,501,504,506,509,512],{"class":360,"line":497},6,[358,499,500],{"class":455},"    Extra       ",[358,502,503],{"class":368},"map[",[358,505,460],{"class":459},[358,507,508],{"class":368},"]",[358,510,511],{"class":372},"any",[358,513,514],{"class":463}," \u002F\u002F claim data beyond the standard fields (e.g. tenant)\n",[358,516,518],{"class":360,"line":517},7,[358,519,520],{"class":368},"}\n",[378,522,523,524,527],{},"It has two query methods, both with ",[386,525,526],{},"any-of"," semantics: one match among several arguments is enough.",[348,529,531],{"className":350,"code":530,"language":352,"meta":353,"style":353},"id.HasRole(\"admin\", \"editor\")       \u002F\u002F true if any of the roles is present\nid.HasPermission(\"orders.view\")     \u002F\u002F true if the permission is present\n",[355,532,533,573],{"__ignoreMap":353},[358,534,535,538,541,545,548,551,555,557,560,562,565,567,570],{"class":360,"line":361},[358,536,537],{"class":455},"id",[358,539,540],{"class":368},".",[358,542,544],{"class":543},"s2Zo4","HasRole",[358,546,547],{"class":368},"(",[358,549,550],{"class":368},"\"",[358,552,554],{"class":553},"sfazB","admin",[358,556,550],{"class":368},[358,558,559],{"class":368},",",[358,561,369],{"class":368},[358,563,564],{"class":553},"editor",[358,566,550],{"class":368},[358,568,569],{"class":368},")",[358,571,572],{"class":463},"       \u002F\u002F true if any of the roles is present\n",[358,574,575,577,579,582,584,586,589,591,593],{"class":360,"line":452},[358,576,537],{"class":455},[358,578,540],{"class":368},[358,580,581],{"class":543},"HasPermission",[358,583,547],{"class":368},[358,585,550],{"class":368},[358,587,588],{"class":553},"orders.view",[358,590,550],{"class":368},[358,592,569],{"class":368},[358,594,595],{"class":463},"     \u002F\u002F true if the permission is present\n",[378,597,598,599,602,603,606,607,610],{},"Both are safe to call ",[386,600,601],{},"on a nil identity"," (they report ",[355,604,605],{},"false","); this deliberately matches the fact that ",[355,608,609],{},"FromContext"," returns nil when the request did not pass auth middleware. Your guard code therefore never needs a nil check:",[348,612,614],{"className":350,"code":613,"language":352,"meta":353,"style":353},"if rbac.FromContext(ctx).HasRole(\"admin\") { \u002F\u002F works on nil too, reports false\n    \u002F\u002F ...\n}\n",[355,615,616,654,659],{"__ignoreMap":353},[358,617,618,621,624,626,628,630,633,636,638,640,642,644,646,648,651],{"class":360,"line":361},[358,619,620],{"class":364},"if",[358,622,623],{"class":455}," rbac",[358,625,540],{"class":368},[358,627,609],{"class":543},[358,629,547],{"class":368},[358,631,632],{"class":455},"ctx",[358,634,635],{"class":368},").",[358,637,544],{"class":543},[358,639,547],{"class":368},[358,641,550],{"class":368},[358,643,554],{"class":553},[358,645,550],{"class":368},[358,647,569],{"class":368},[358,649,650],{"class":368}," {",[358,652,653],{"class":463}," \u002F\u002F works on nil too, reports false\n",[358,655,656],{"class":360,"line":452},[358,657,658],{"class":463},"    \u002F\u002F ...\n",[358,660,661],{"class":360,"line":467},[358,662,520],{"class":368},[422,664,666],{"id":665},"withidentity-and-fromcontext","WithIdentity and FromContext",[378,668,380,669,671,672,675],{},[355,670,390],{}," travels in a plain ",[355,673,674],{},"context.Context",":",[348,677,679],{"className":350,"code":678,"language":352,"meta":353,"style":353},"ctx = rbac.WithIdentity(ctx, id) \u002F\u002F called by the auth middleware\nid := rbac.FromContext(ctx)      \u002F\u002F *rbac.Identity, or nil without auth\n",[355,680,681,710],{"__ignoreMap":353},[358,682,683,686,689,691,693,696,698,700,702,705,707],{"class":360,"line":361},[358,684,685],{"class":455},"ctx ",[358,687,688],{"class":368},"=",[358,690,623],{"class":455},[358,692,540],{"class":368},[358,694,695],{"class":543},"WithIdentity",[358,697,547],{"class":368},[358,699,632],{"class":455},[358,701,559],{"class":368},[358,703,704],{"class":455}," id",[358,706,569],{"class":368},[358,708,709],{"class":463}," \u002F\u002F called by the auth middleware\n",[358,711,712,715,718,720,722,724,726,728,730],{"class":360,"line":452},[358,713,714],{"class":455},"id ",[358,716,717],{"class":368},":=",[358,719,623],{"class":455},[358,721,540],{"class":368},[358,723,609],{"class":543},[358,725,547],{"class":368},[358,727,632],{"class":455},[358,729,569],{"class":368},[358,731,732],{"class":463},"      \u002F\u002F *rbac.Identity, or nil without auth\n",[378,734,735,737,738,740,741,743,744,747,748,740,751,754],{},[355,736,390],{},", ",[355,739,695],{}," and ",[355,742,609],{}," are ",[386,745,746],{},"framework-agnostic",": they import neither Fiber nor net\u002Fhttp. The same contract therefore works outside the HTTP layer too: in services, ",[393,749,750],{"href":178},"listeners",[393,752,753],{"href":188},"jobs",", whenever an identity was put into the context.",[378,756,757],{},"In the shop, for example, order listing narrows to the caller at the service layer: an admin sees everything, a customer only their own.",[348,759,761],{"className":350,"code":760,"language":352,"meta":353,"style":353},"\u002F\u002F internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fservice\u002Forders.go\nfunc (s *Service) ListOrders(ctx context.Context, cursor string) ([]Order, error) {\n    id := rbac.FromContext(ctx)\n    if id.HasRole(\"admin\") {\n        return s.orders.ListAll(ctx, cursor)\n    }\n    return s.orders.ListByUser(ctx, id.Subject, cursor)\n}\n",[355,762,763,768,828,848,871,899,904,939],{"__ignoreMap":353},[358,764,765],{"class":360,"line":361},[358,766,767],{"class":463},"\u002F\u002F internal\u002Fmodules\u002Fshop\u002Fsurfaces\u002Fapi\u002Fservice\u002Forders.go\n",[358,769,770,773,776,780,783,786,788,791,793,795,798,800,803,805,808,811,813,816,819,821,824,826],{"class":360,"line":452},[358,771,772],{"class":368},"func",[358,774,775],{"class":368}," (",[358,777,779],{"class":778},"sHdIc","s ",[358,781,782],{"class":368},"*",[358,784,785],{"class":372},"Service",[358,787,569],{"class":368},[358,789,790],{"class":543}," ListOrders",[358,792,547],{"class":368},[358,794,632],{"class":778},[358,796,797],{"class":372}," context",[358,799,540],{"class":368},[358,801,802],{"class":372},"Context",[358,804,559],{"class":368},[358,806,807],{"class":778}," cursor",[358,809,810],{"class":459}," string",[358,812,569],{"class":368},[358,814,815],{"class":368}," ([]",[358,817,818],{"class":372},"Order",[358,820,559],{"class":368},[358,822,823],{"class":459}," error",[358,825,569],{"class":368},[358,827,449],{"class":368},[358,829,830,833,835,837,839,841,843,845],{"class":360,"line":467},[358,831,832],{"class":455},"    id ",[358,834,717],{"class":368},[358,836,623],{"class":455},[358,838,540],{"class":368},[358,840,609],{"class":543},[358,842,547],{"class":368},[358,844,632],{"class":455},[358,846,847],{"class":368},")\n",[358,849,850,853,855,857,859,861,863,865,867,869],{"class":360,"line":476},[358,851,852],{"class":364},"    if",[358,854,704],{"class":455},[358,856,540],{"class":368},[358,858,544],{"class":543},[358,860,547],{"class":368},[358,862,550],{"class":368},[358,864,554],{"class":553},[358,866,550],{"class":368},[358,868,569],{"class":368},[358,870,449],{"class":368},[358,872,873,876,879,881,884,886,889,891,893,895,897],{"class":360,"line":487},[358,874,875],{"class":364},"        return",[358,877,878],{"class":455}," s",[358,880,540],{"class":368},[358,882,883],{"class":455},"orders",[358,885,540],{"class":368},[358,887,888],{"class":543},"ListAll",[358,890,547],{"class":368},[358,892,632],{"class":455},[358,894,559],{"class":368},[358,896,807],{"class":455},[358,898,847],{"class":368},[358,900,901],{"class":360,"line":497},[358,902,903],{"class":368},"    }\n",[358,905,906,909,911,913,915,917,920,922,924,926,928,930,933,935,937],{"class":360,"line":517},[358,907,908],{"class":364},"    return",[358,910,878],{"class":455},[358,912,540],{"class":368},[358,914,883],{"class":455},[358,916,540],{"class":368},[358,918,919],{"class":543},"ListByUser",[358,921,547],{"class":368},[358,923,632],{"class":455},[358,925,559],{"class":368},[358,927,704],{"class":455},[358,929,540],{"class":368},[358,931,932],{"class":455},"Subject",[358,934,559],{"class":368},[358,936,807],{"class":455},[358,938,847],{"class":368},[358,940,942],{"class":360,"line":941},8,[358,943,520],{"class":368},[378,945,946,947,950,951,955,956,540],{},"This is ",[386,948,949],{},"data scoping"," (what to show), not an access decision (whether it is allowed at all). Per-request authorization, meaning \"may they view ",[952,953,954],"em",{},"this particular"," order\", is the job of ",[393,957,958],{"href":214},"policies",[422,960,962],{"id":961},"route-guards","Route guards",[378,964,380,965,409,968,971,972,974,975,977],{},[355,966,967],{},"RequireRole",[355,969,970],{},"RequirePermission"," middlewares protect a whole route group. They assume the ",[393,973,395],{"href":204}," already ran before them; they only inspect the ",[355,976,390],{}," in the context.",[979,980,981,1161],"code-group",{},[348,982,985],{"className":350,"code":983,"filename":984,"language":352,"meta":353,"style":353},"adminGroup := router.Group(\"\u002Fadmin\u002Fshop\")\nadminGroup.Use(\n    auth.Middleware(deps.Auth),      \u002F\u002F Bearer → parse → Identity into context\n    rbac.RequireRole(\"admin\"),       \u002F\u002F 401 without identity, 403 without the role\n)\n\n\u002F\u002F permission-based variant, with any-of semantics:\nreports := router.Group(\"\u002Freports\", auth.Middleware(deps.Auth),\n    rbac.RequirePermission(\"reports.view\", \"reports.export\"))\n","Fiber",[355,986,987,1013,1026,1052,1074,1078,1084,1089,1131],{"__ignoreMap":353},[358,988,989,992,994,997,999,1002,1004,1006,1009,1011],{"class":360,"line":361},[358,990,991],{"class":455},"adminGroup ",[358,993,717],{"class":368},[358,995,996],{"class":455}," router",[358,998,540],{"class":368},[358,1000,1001],{"class":543},"Group",[358,1003,547],{"class":368},[358,1005,550],{"class":368},[358,1007,1008],{"class":553},"\u002Fadmin\u002Fshop",[358,1010,550],{"class":368},[358,1012,847],{"class":368},[358,1014,1015,1018,1020,1023],{"class":360,"line":452},[358,1016,1017],{"class":455},"adminGroup",[358,1019,540],{"class":368},[358,1021,1022],{"class":543},"Use",[358,1024,1025],{"class":368},"(\n",[358,1027,1028,1031,1033,1036,1038,1041,1043,1046,1049],{"class":360,"line":467},[358,1029,1030],{"class":455},"    auth",[358,1032,540],{"class":368},[358,1034,1035],{"class":543},"Middleware",[358,1037,547],{"class":368},[358,1039,1040],{"class":455},"deps",[358,1042,540],{"class":368},[358,1044,1045],{"class":455},"Auth",[358,1047,1048],{"class":368},"),",[358,1050,1051],{"class":463},"      \u002F\u002F Bearer → parse → Identity into context\n",[358,1053,1054,1057,1059,1061,1063,1065,1067,1069,1071],{"class":360,"line":476},[358,1055,1056],{"class":455},"    rbac",[358,1058,540],{"class":368},[358,1060,967],{"class":543},[358,1062,547],{"class":368},[358,1064,550],{"class":368},[358,1066,554],{"class":553},[358,1068,550],{"class":368},[358,1070,1048],{"class":368},[358,1072,1073],{"class":463},"       \u002F\u002F 401 without identity, 403 without the role\n",[358,1075,1076],{"class":360,"line":487},[358,1077,847],{"class":368},[358,1079,1080],{"class":360,"line":497},[358,1081,1083],{"emptyLinePlaceholder":1082},true,"\n",[358,1085,1086],{"class":360,"line":517},[358,1087,1088],{"class":463},"\u002F\u002F permission-based variant, with any-of semantics:\n",[358,1090,1091,1094,1096,1098,1100,1102,1104,1106,1109,1111,1113,1116,1118,1120,1122,1124,1126,1128],{"class":360,"line":941},[358,1092,1093],{"class":455},"reports ",[358,1095,717],{"class":368},[358,1097,996],{"class":455},[358,1099,540],{"class":368},[358,1101,1001],{"class":543},[358,1103,547],{"class":368},[358,1105,550],{"class":368},[358,1107,1108],{"class":553},"\u002Freports",[358,1110,550],{"class":368},[358,1112,559],{"class":368},[358,1114,1115],{"class":455}," auth",[358,1117,540],{"class":368},[358,1119,1035],{"class":543},[358,1121,547],{"class":368},[358,1123,1040],{"class":455},[358,1125,540],{"class":368},[358,1127,1045],{"class":455},[358,1129,1130],{"class":368},"),\n",[358,1132,1134,1136,1138,1140,1142,1144,1147,1149,1151,1153,1156,1158],{"class":360,"line":1133},9,[358,1135,1056],{"class":455},[358,1137,540],{"class":368},[358,1139,970],{"class":543},[358,1141,547],{"class":368},[358,1143,550],{"class":368},[358,1145,1146],{"class":553},"reports.view",[358,1148,550],{"class":368},[358,1150,559],{"class":368},[358,1152,369],{"class":368},[358,1154,1155],{"class":553},"reports.export",[358,1157,550],{"class":368},[358,1159,1160],{"class":368},"))\n",[348,1162,1165],{"className":350,"code":1163,"filename":1164,"language":352,"meta":353,"style":353},"router.Route(\"\u002Fadmin\u002Fshop\", func(r chi.Router) {\n    r.Use(\n        auth.MiddlewareHTTP(deps.Auth),  \u002F\u002F Bearer → parse → Identity into context\n        rbac.RequireRoleHTTP(\"admin\"),   \u002F\u002F 401 without identity, 403 without the role\n    )\n    \u002F\u002F ...\n})\n\n\u002F\u002F permission-based variant, with any-of semantics:\nrouter.Route(\"\u002Freports\", func(r chi.Router) {\n    r.Use(auth.MiddlewareHTTP(deps.Auth),\n        rbac.RequirePermissionHTTP(\"reports.view\", \"reports.export\"))\n    \u002F\u002F ...\n})\n","chi",[355,1166,1167,1205,1216,1239,1262,1267,1271,1276,1280,1284,1317,1345,1373,1378],{"__ignoreMap":353},[358,1168,1169,1172,1174,1177,1179,1181,1183,1185,1187,1190,1193,1196,1198,1201,1203],{"class":360,"line":361},[358,1170,1171],{"class":455},"router",[358,1173,540],{"class":368},[358,1175,1176],{"class":543},"Route",[358,1178,547],{"class":368},[358,1180,550],{"class":368},[358,1182,1008],{"class":553},[358,1184,550],{"class":368},[358,1186,559],{"class":368},[358,1188,1189],{"class":368}," func(",[358,1191,1192],{"class":778},"r",[358,1194,1195],{"class":372}," chi",[358,1197,540],{"class":368},[358,1199,1200],{"class":372},"Router",[358,1202,569],{"class":368},[358,1204,449],{"class":368},[358,1206,1207,1210,1212,1214],{"class":360,"line":452},[358,1208,1209],{"class":455},"    r",[358,1211,540],{"class":368},[358,1213,1022],{"class":543},[358,1215,1025],{"class":368},[358,1217,1218,1221,1223,1226,1228,1230,1232,1234,1236],{"class":360,"line":467},[358,1219,1220],{"class":455},"        auth",[358,1222,540],{"class":368},[358,1224,1225],{"class":543},"MiddlewareHTTP",[358,1227,547],{"class":368},[358,1229,1040],{"class":455},[358,1231,540],{"class":368},[358,1233,1045],{"class":455},[358,1235,1048],{"class":368},[358,1237,1238],{"class":463},"  \u002F\u002F Bearer → parse → Identity into context\n",[358,1240,1241,1244,1246,1249,1251,1253,1255,1257,1259],{"class":360,"line":476},[358,1242,1243],{"class":455},"        rbac",[358,1245,540],{"class":368},[358,1247,1248],{"class":543},"RequireRoleHTTP",[358,1250,547],{"class":368},[358,1252,550],{"class":368},[358,1254,554],{"class":553},[358,1256,550],{"class":368},[358,1258,1048],{"class":368},[358,1260,1261],{"class":463},"   \u002F\u002F 401 without identity, 403 without the role\n",[358,1263,1264],{"class":360,"line":487},[358,1265,1266],{"class":368},"    )\n",[358,1268,1269],{"class":360,"line":497},[358,1270,658],{"class":463},[358,1272,1273],{"class":360,"line":517},[358,1274,1275],{"class":368},"})\n",[358,1277,1278],{"class":360,"line":941},[358,1279,1083],{"emptyLinePlaceholder":1082},[358,1281,1282],{"class":360,"line":1133},[358,1283,1088],{"class":463},[358,1285,1287,1289,1291,1293,1295,1297,1299,1301,1303,1305,1307,1309,1311,1313,1315],{"class":360,"line":1286},10,[358,1288,1171],{"class":455},[358,1290,540],{"class":368},[358,1292,1176],{"class":543},[358,1294,547],{"class":368},[358,1296,550],{"class":368},[358,1298,1108],{"class":553},[358,1300,550],{"class":368},[358,1302,559],{"class":368},[358,1304,1189],{"class":368},[358,1306,1192],{"class":778},[358,1308,1195],{"class":372},[358,1310,540],{"class":368},[358,1312,1200],{"class":372},[358,1314,569],{"class":368},[358,1316,449],{"class":368},[358,1318,1320,1322,1324,1326,1328,1331,1333,1335,1337,1339,1341,1343],{"class":360,"line":1319},11,[358,1321,1209],{"class":455},[358,1323,540],{"class":368},[358,1325,1022],{"class":543},[358,1327,547],{"class":368},[358,1329,1330],{"class":455},"auth",[358,1332,540],{"class":368},[358,1334,1225],{"class":543},[358,1336,547],{"class":368},[358,1338,1040],{"class":455},[358,1340,540],{"class":368},[358,1342,1045],{"class":455},[358,1344,1130],{"class":368},[358,1346,1348,1350,1352,1355,1357,1359,1361,1363,1365,1367,1369,1371],{"class":360,"line":1347},12,[358,1349,1243],{"class":455},[358,1351,540],{"class":368},[358,1353,1354],{"class":543},"RequirePermissionHTTP",[358,1356,547],{"class":368},[358,1358,550],{"class":368},[358,1360,1146],{"class":553},[358,1362,550],{"class":368},[358,1364,559],{"class":368},[358,1366,369],{"class":368},[358,1368,1155],{"class":553},[358,1370,550],{"class":368},[358,1372,1160],{"class":368},[358,1374,1376],{"class":360,"line":1375},13,[358,1377,658],{"class":463},[358,1379,1381],{"class":360,"line":1380},14,[358,1382,1275],{"class":368},[378,1384,1385,1386,675],{},"The semantics are identical on both engines, and the responses are ",[393,1387,1388],{"href":119},"RFC 9457 problem documents",[1390,1391,1392,1405],"table",{},[1393,1394,1395],"thead",{},[1396,1397,1398,1402],"tr",{},[1399,1400,1401],"th",{},"Situation",[1399,1403,1404],{},"Result",[1406,1407,1408,1426,1439],"tbody",{},[1396,1409,1410,1417],{},[1411,1412,1413,1414,1416],"td",{},"no ",[355,1415,390],{}," in the context (auth did not run)",[1411,1418,1419,1422,1423],{},[386,1420,1421],{},"401"," ",[355,1424,1425],{},"authentication required",[1396,1427,1428,1431],{},[1411,1429,1430],{},"identity present, but none of the listed roles\u002Fpermissions held",[1411,1432,1433,1422,1436],{},[386,1434,1435],{},"403",[355,1437,1438],{},"insufficient privileges",[1396,1440,1441,1444],{},[1411,1442,1443],{},"any of the listed roles\u002Fpermissions held (any-of)",[1411,1445,1446],{},"pass",[422,1448,1450],{"id":1449},"the-shops-admin-surface","The shop's admin surface",[378,1452,1453,1454,1456,1457,1460,1461,1463,1464,1467,1468,1471,1472,1475,1476,540],{},"In the shop, the entire ",[355,1455,554],{}," surface (product CRUD, image upload) sits behind the pair above: the middleware goes into the ",[355,1458,1459],{},"RegisterAdmin"," function generated by ",[355,1462,314],{},", so the protection is surface-wide and the sibling ",[355,1465,1466],{},"api"," surface is untouched. The exact wiring (and adding the ",[355,1469,1470],{},"deps.Auth"," field) is shown on the ",[393,1473,1474],{"href":204},"authentication"," page; the surface-per-function structure is covered by ",[393,1477,314],{"href":315},[378,1479,1480,1481,1484],{},"For finer granularity the same guards can go on a smaller group instead of the whole surface, but once the decision is no longer \"do they have this role\" but \"do they own this resource\", it is not RBAC anymore: that is what the ",[393,1482,1483],{"href":214},"policy layer"," is for.",[422,1486,1488],{"id":1487},"where-roles-and-permissions-come-from","Where roles and permissions come from",[378,1490,1491,1492,1495,1496,1498,1499,1501,1502,1504,1505,1507,1508,1511],{},"In the kit's default setup, from the JWT claims: the login endpoint writes the user's roles and permissions into ",[355,1493,1494],{},"DefaultClaims",", and the auth middleware fills the ",[355,1497,390],{}," fields from them. The full chain is on the ",[393,1500,1474],{"href":204}," page. Since ",[355,1503,383],{}," only ever sees the ",[355,1506,390],{},", the source is swappable: a custom claims type (",[355,1509,1510],{},"MiddlewareFor","), an identity built from an API key, or a test fixture all work the same way.",[1513,1514,1515,1522],"tip",{},[378,1516,1517,1518,1521],{},"Tests need no HTTP: with ",[355,1519,1520],{},"rbac.WithIdentity"," you can put the desired identity directly into the context, and the service layer behaves as if the middleware had populated it.",[348,1523,1525],{"className":350,"code":1524,"language":352,"meta":353,"style":353},"ctx := rbac.WithIdentity(t.Context(), &rbac.Identity{\n    Subject: \"u-42\", Roles: []string{\"admin\"},\n})\n",[355,1526,1527,1563,1601],{"__ignoreMap":353},[358,1528,1529,1531,1533,1535,1537,1539,1541,1544,1546,1548,1551,1554,1556,1558,1560],{"class":360,"line":361},[358,1530,685],{"class":455},[358,1532,717],{"class":368},[358,1534,623],{"class":455},[358,1536,540],{"class":368},[358,1538,695],{"class":543},[358,1540,547],{"class":368},[358,1542,1543],{"class":455},"t",[358,1545,540],{"class":368},[358,1547,802],{"class":543},[358,1549,1550],{"class":368},"(),",[358,1552,1553],{"class":368}," &",[358,1555,383],{"class":372},[358,1557,540],{"class":368},[358,1559,390],{"class":372},[358,1561,1562],{"class":368},"{\n",[358,1564,1565,1568,1570,1572,1575,1577,1579,1582,1584,1587,1589,1592,1594,1596,1598],{"class":360,"line":452},[358,1566,1567],{"class":455},"    Subject",[358,1569,675],{"class":368},[358,1571,369],{"class":368},[358,1573,1574],{"class":553},"u-42",[358,1576,550],{"class":368},[358,1578,559],{"class":368},[358,1580,1581],{"class":455}," Roles",[358,1583,675],{"class":368},[358,1585,1586],{"class":368}," []",[358,1588,460],{"class":459},[358,1590,1591],{"class":368},"{",[358,1593,550],{"class":368},[358,1595,554],{"class":553},[358,1597,550],{"class":368},[358,1599,1600],{"class":368},"},\n",[358,1602,1603],{"class":360,"line":467},[358,1604,1275],{"class":368},[378,1606,1607,1608,1611,1612,540],{},"A role says what the caller may do ",[952,1609,1610],{},"in general",". When the decision also needs the concrete request (are they the owner, are they in the same tenant, is the record in the right state), move on to ",[393,1613,958],{"href":214},[422,1615,1617],{"id":1616},"patterns-used","Patterns used",[1619,1620,1621,1636],"ul",{},[1622,1623,1624,775,1627,1629,1630,1632,1633,540],"li",{},[386,1625,1626],{},"Context-injected identity",[355,1628,695],{},"\u002F",[355,1631,609],{},", unexported key, nil-safe methods): ",[393,1634,92],{"href":1635},"\u002Fen\u002Fconcepts\u002Fdesign-patterns#context-injected-dependency",[1622,1637,1638,775,1641,1644,1645,740,1647,1649,1650,540],{},[386,1639,1640],{},"Higher-order authorization guard",[355,1642,1643],{},"requireFn",", shared by both ",[355,1646,967],{},[355,1648,970],{},"): ",[393,1651,92],{"href":1652},"\u002Fen\u002Fconcepts\u002Fdesign-patterns#higher-order-authorization-guard",[1654,1655,1656],"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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .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 .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 .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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}",{"title":353,"searchDepth":452,"depth":452,"links":1658},[1659,1660,1661,1662,1663,1664],{"id":424,"depth":452,"text":425},{"id":665,"depth":452,"text":666},{"id":961,"depth":452,"text":962},{"id":1449,"depth":452,"text":1450},{"id":1487,"depth":452,"text":1488},{"id":1616,"depth":452,"text":1617},"Role and permission checks over the Identity carried in the context: with route guards and service-layer queries.","md",null,{},{"icon":211},{"title":208,"description":1665},"AFsKECM6Lq94cm7rFjjkq0RbdUx78JsydqWH_Z4bnug",[1673,1675],{"title":203,"path":204,"stem":205,"description":1674,"icon":206,"children":-1},"JWT issuing and the Bearer middleware: stateless auth.",{"title":213,"path":214,"stem":215,"description":1676,"icon":216,"children":-1},"Per-request, per-user authorization above roles: declared in the contract, enforced from generated code.",1784668709947]