[{"data":1,"prerenderedAt":2713},["ShallowReactive",2],{"navigation_docs_en":3,"-en-auth-standalone":441,"-en-auth-standalone-surround":2708},[4,45,60,81,112,129,146,173,197,213,242,279,356,372,384,403,429],{"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},"queue","i-lucide-layers","\u002Fen\u002Fqueue","en\u002F10.queue",[51,56],{"title":52,"path":53,"stem":54,"icon":55},"Overview","\u002Fen\u002Fqueue\u002Foverview","en\u002F10.queue\u002F1.overview","i-lucide-list-ordered",{"title":57,"path":58,"stem":59,"icon":47},"Tasks","\u002Fen\u002Fqueue\u002Ftasks","en\u002F10.queue\u002F2.tasks",{"title":61,"icon":62,"path":63,"stem":64,"children":65,"page":44},"events","i-lucide-workflow","\u002Fen\u002Fevents","en\u002F11.events",[66,71,76],{"title":67,"path":68,"stem":69,"icon":70},"Events","\u002Fen\u002Fevents\u002Foverview","en\u002F11.events\u002F1.overview","i-lucide-radio",{"title":72,"path":73,"stem":74,"icon":75},"Outbox","\u002Fen\u002Fevents\u002Foutbox","en\u002F11.events\u002F2.outbox","i-lucide-inbox",{"title":77,"path":78,"stem":79,"icon":80},"Scheduling","\u002Fen\u002Fevents\u002Fscheduler","en\u002F11.events\u002F3.scheduler","i-lucide-calendar-clock",{"title":82,"icon":83,"path":84,"stem":85,"children":86,"page":44},"auth","i-lucide-shield-check","\u002Fen\u002Fauth","en\u002F12.auth",[87,92,97,102,107],{"title":88,"path":89,"stem":90,"icon":91},"Authentication","\u002Fen\u002Fauth\u002Foverview","en\u002F12.auth\u002F1.overview","i-lucide-key-round",{"title":93,"path":94,"stem":95,"icon":96},"RBAC","\u002Fen\u002Fauth\u002Frbac","en\u002F12.auth\u002F2.rbac","i-lucide-users",{"title":98,"path":99,"stem":100,"icon":101},"Policies","\u002Fen\u002Fauth\u002Fpolicy","en\u002F12.auth\u002F3.policy","i-lucide-gavel",{"title":103,"path":104,"stem":105,"icon":106},"Using auth standalone","\u002Fen\u002Fauth\u002Fstandalone","en\u002F12.auth\u002F4.standalone","i-lucide-plug",{"title":108,"path":109,"stem":110,"icon":111},"Social login with goth, standalone","\u002Fen\u002Fauth\u002Fsocial-standalone","en\u002F12.auth\u002F5.social-standalone","i-lucide-log-in",{"title":113,"icon":114,"path":115,"stem":116,"children":117,"page":44},"mail","i-lucide-mail","\u002Fen\u002Fmail","en\u002F13.mail",[118,121,125],{"title":52,"path":119,"stem":120,"icon":114},"\u002Fen\u002Fmail\u002Foverview","en\u002F13.mail\u002F1.overview",{"title":122,"path":123,"stem":124,"icon":114},"SMTP","\u002Fen\u002Fmail\u002Fsmtp","en\u002F13.mail\u002F2.smtp",{"title":126,"path":127,"stem":128,"icon":114},"MJML","\u002Fen\u002Fmail\u002Fmjml","en\u002F13.mail\u002F3.mjml",{"title":130,"icon":131,"path":132,"stem":133,"children":134,"page":44},"notify","i-lucide-bell","\u002Fen\u002Fnotify","en\u002F14.notify",[135,138,142],{"title":52,"path":136,"stem":137,"icon":131},"\u002Fen\u002Fnotify\u002Foverview","en\u002F14.notify\u002F1.overview",{"title":139,"path":140,"stem":141,"icon":131},"Database channel","\u002Fen\u002Fnotify\u002Fdatabase","en\u002F14.notify\u002F2.database",{"title":143,"path":144,"stem":145,"icon":131},"Broadcast channel","\u002Fen\u002Fnotify\u002Fbroadcast","en\u002F14.notify\u002F3.broadcast",{"title":147,"icon":148,"path":149,"stem":150,"children":151,"page":44},"storage","i-lucide-hard-drive","\u002Fen\u002Fstorage","en\u002F15.storage",[152,155,159,163,168],{"title":52,"path":153,"stem":154,"icon":148},"\u002Fen\u002Fstorage\u002Foverview","en\u002F15.storage\u002F1.overview",{"title":156,"path":157,"stem":158},"Disk API","\u002Fen\u002Fstorage\u002Fdisk-api","en\u002F15.storage\u002F2.disk-api",{"title":160,"path":161,"stem":162,"icon":148},"Memory and local disk","\u002Fen\u002Fstorage\u002Flocal","en\u002F15.storage\u002F3.local",{"title":164,"path":165,"stem":166,"icon":167},"S3","\u002Fen\u002Fstorage\u002Fs3","en\u002F15.storage\u002F4.s3","i-lucide-cloud",{"title":169,"path":170,"stem":171,"icon":172},"Testing","\u002Fen\u002Fstorage\u002Ftesting","en\u002F15.storage\u002F5.testing","i-lucide-flask-conical",{"title":174,"icon":175,"path":176,"stem":177,"children":178,"page":44},"telemetry","i-lucide-activity","\u002Fen\u002Ftelemetry","en\u002F16.telemetry",[179,182,187,192],{"title":52,"path":180,"stem":181,"icon":175},"\u002Fen\u002Ftelemetry\u002Foverview","en\u002F16.telemetry\u002F1.overview",{"title":183,"path":184,"stem":185,"icon":186},"Logging","\u002Fen\u002Ftelemetry\u002Flogging","en\u002F16.telemetry\u002F2.logging","i-lucide-scroll-text",{"title":188,"path":189,"stem":190,"icon":191},"OpenTelemetry","\u002Fen\u002Ftelemetry\u002Fopentelemetry","en\u002F16.telemetry\u002F3.opentelemetry","i-lucide-radar",{"title":193,"path":194,"stem":195,"icon":196},"Sentry","\u002Fen\u002Ftelemetry\u002Fsentry","en\u002F16.telemetry\u002F4.sentry","i-lucide-bug",{"title":198,"icon":199,"path":200,"stem":201,"children":202,"page":44},"Reference","i-lucide-book-open","\u002Fen\u002Freference","en\u002F17.reference",[203,208],{"title":204,"path":205,"stem":206,"icon":207},"Configuration","\u002Fen\u002Freference\u002Fconfiguration","en\u002F17.reference\u002F1.configuration","i-lucide-settings",{"title":209,"path":210,"stem":211,"icon":212},"External dependencies","\u002Fen\u002Freference\u002Fdependencies","en\u002F17.reference\u002F2.dependencies","i-lucide-package",{"title":214,"icon":215,"path":216,"stem":217,"children":218,"page":44},"Concepts","i-lucide-lightbulb","\u002Fen\u002Fconcepts","en\u002F2.concepts",[219,223,228,232,237],{"title":220,"path":221,"stem":222,"icon":47},"Architecture","\u002Fen\u002Fconcepts\u002Farchitecture","en\u002F2.concepts\u002F1.architecture",{"title":224,"path":225,"stem":226,"icon":227},"Error model","\u002Fen\u002Fconcepts\u002Ferror-model","en\u002F2.concepts\u002F2.error-model","i-lucide-shield-alert",{"title":204,"path":229,"stem":230,"icon":231},"\u002Fen\u002Fconcepts\u002Fconfiguration","en\u002F2.concepts\u002F3.configuration","i-lucide-settings-2",{"title":233,"path":234,"stem":235,"icon":236},"Codegen pipeline","\u002Fen\u002Fconcepts\u002Fcodegen-pipeline","en\u002F2.concepts\u002F4.codegen-pipeline","i-lucide-file-json",{"title":238,"path":239,"stem":240,"icon":241},"Design patterns","\u002Fen\u002Fconcepts\u002Fdesign-patterns","en\u002F2.concepts\u002F5.design-patterns","i-lucide-puzzle",{"title":243,"icon":244,"path":245,"stem":246,"children":247,"page":44},"Kit","i-lucide-box","\u002Fen\u002Fkit","en\u002F3.kit",[248,251,256,261,266,270,275],{"title":52,"path":249,"stem":250,"icon":244},"\u002Fen\u002Fkit\u002Foverview","en\u002F3.kit\u002F1.overview",{"title":252,"path":253,"stem":254,"icon":255},"Application lifecycle","\u002Fen\u002Fkit\u002Fapp","en\u002F3.kit\u002F2.app","i-lucide-power",{"title":257,"path":258,"stem":259,"icon":260},"Server","\u002Fen\u002Fkit\u002Fserver","en\u002F3.kit\u002F3.server","i-lucide-server",{"title":262,"path":263,"stem":264,"icon":265},"Worker","\u002Fen\u002Fkit\u002Fworker","en\u002F3.kit\u002F4.worker","i-lucide-cog",{"title":267,"path":268,"stem":269,"icon":70},"Realtime","\u002Fen\u002Fkit\u002Frealtime","en\u002F3.kit\u002F5.realtime",{"title":271,"path":272,"stem":273,"icon":274},"Migrations","\u002Fen\u002Fkit\u002Fmigrations","en\u002F3.kit\u002F6.migrations","i-lucide-file-stack",{"title":276,"path":277,"stem":278,"icon":111},"Social login","\u002Fen\u002Fkit\u002Fsocial-login","en\u002F3.kit\u002F7.social-login",{"title":280,"icon":281,"path":282,"stem":283,"children":284,"page":44},"CLI Reference","i-lucide-terminal","\u002Fen\u002Fcli","en\u002F4.cli",[285,288,293,298,303,308,313,318,323,328,333,337,342,347,351],{"title":52,"path":286,"stem":287,"icon":281},"\u002Fen\u002Fcli\u002Foverview","en\u002F4.cli\u002F1.overview",{"title":289,"path":290,"stem":291,"icon":292},"add db","\u002Fen\u002Fcli\u002Fadd-db","en\u002F4.cli\u002F10.add-db","i-lucide-database-zap",{"title":294,"path":295,"stem":296,"icon":297},"add compose \u002F add docker","\u002Fen\u002Fcli\u002Fadd-compose","en\u002F4.cli\u002F11.add-compose","i-lucide-container",{"title":299,"path":300,"stem":301,"icon":302},"add mail","\u002Fen\u002Fcli\u002Fadd-mail","en\u002F4.cli\u002F12.add-mail","i-lucide-mail-plus",{"title":304,"path":305,"stem":306,"icon":307},"add notification","\u002Fen\u002Fcli\u002Fadd-notification","en\u002F4.cli\u002F13.add-notification","i-lucide-bell-plus",{"title":309,"path":310,"stem":311,"icon":312},"add realtime","\u002Fen\u002Fcli\u002Fadd-realtime","en\u002F4.cli\u002F14.add-realtime","i-lucide-radio-tower",{"title":314,"path":315,"stem":316,"icon":317},"add seeder","\u002Fen\u002Fcli\u002Fadd-seeder","en\u002F4.cli\u002F15.add-seeder","i-lucide-sprout",{"title":319,"path":320,"stem":321,"icon":322},"new project","\u002Fen\u002Fcli\u002Fnew-project","en\u002F4.cli\u002F2.new-project","i-lucide-folder-plus",{"title":324,"path":325,"stem":326,"icon":327},"new module","\u002Fen\u002Fcli\u002Fnew-module","en\u002F4.cli\u002F3.new-module","i-lucide-blocks",{"title":329,"path":330,"stem":331,"icon":332},"add surface","\u002Fen\u002Fcli\u002Fadd-surface","en\u002F4.cli\u002F4.add-surface","i-lucide-layers-2",{"title":334,"path":335,"stem":336,"icon":244},"add core","\u002Fen\u002Fcli\u002Fadd-core","en\u002F4.cli\u002F5.add-core",{"title":338,"path":339,"stem":340,"icon":341},"add handler","\u002Fen\u002Fcli\u002Fadd-handler","en\u002F4.cli\u002F6.add-handler","i-lucide-webhook",{"title":343,"path":344,"stem":345,"icon":346},"new migration","\u002Fen\u002Fcli\u002Fnew-migration","en\u002F4.cli\u002F7.new-migration","i-lucide-file-plus",{"title":348,"path":349,"stem":350,"icon":265},"worker generators","\u002Fen\u002Fcli\u002Fworker-generators","en\u002F4.cli\u002F8.worker-generators",{"title":352,"path":353,"stem":354,"icon":355},"upgrade templates","\u002Fen\u002Fcli\u002Fupgrade-templates","en\u002F4.cli\u002F9.upgrade-templates","i-lucide-refresh-cw",{"title":357,"icon":227,"path":358,"stem":359,"children":360,"page":44},"errs","\u002Fen\u002Ferrs","en\u002F5.errs",[361,364,368],{"title":52,"path":362,"stem":363,"icon":227},"\u002Fen\u002Ferrs\u002Foverview","en\u002F5.errs\u002F1.overview",{"title":365,"path":366,"stem":367},"API","\u002Fen\u002Ferrs\u002Fapi","en\u002F5.errs\u002F2.api",{"title":369,"path":370,"stem":371,"icon":227},"Integration","\u002Fen\u002Ferrs\u002Fintegration","en\u002F5.errs\u002F3.integration",{"title":373,"icon":231,"path":374,"stem":375,"children":376,"page":44},"envconf","\u002Fen\u002Fenvconf","en\u002F6.envconf",[377,380],{"title":52,"path":378,"stem":379,"icon":231},"\u002Fen\u002Fenvconf\u002Foverview","en\u002F6.envconf\u002F1.overview",{"title":381,"path":382,"stem":383},"Recipes","\u002Fen\u002Fenvconf\u002Frecipes","en\u002F6.envconf\u002F2.recipes",{"title":385,"icon":386,"path":387,"stem":388,"children":389,"page":44},"httperr","i-lucide-globe","\u002Fen\u002Fhttperr","en\u002F7.httperr",[390,393,398],{"title":52,"path":391,"stem":392,"icon":386},"\u002Fen\u002Fhttperr\u002Foverview","en\u002F7.httperr\u002F1.overview",{"title":394,"path":395,"stem":396,"icon":397},"Error responses","\u002Fen\u002Fhttperr\u002Ferror-responses","en\u002F7.httperr\u002F2.error-responses","i-lucide-octagon-alert",{"title":399,"path":400,"stem":401,"icon":402},"Validation","\u002Fen\u002Fhttperr\u002Fvalidation","en\u002F7.httperr\u002F3.validation","i-lucide-badge-check",{"title":404,"icon":405,"path":406,"stem":407,"children":408,"page":44},"dbx","i-lucide-database","\u002Fen\u002Fdbx","en\u002F8.dbx",[409,412,416,420,425],{"title":52,"path":410,"stem":411,"icon":405},"\u002Fen\u002Fdbx\u002Foverview","en\u002F8.dbx\u002F1.overview",{"title":413,"path":414,"stem":415,"icon":106},"pgx","\u002Fen\u002Fdbx\u002Fpg","en\u002F8.dbx\u002F2.pg",{"title":417,"path":418,"stem":419,"icon":47},"bun","\u002Fen\u002Fdbx\u002Fbunx","en\u002F8.dbx\u002F3.bunx",{"title":421,"path":422,"stem":423,"icon":424},"Transactions","\u002Fen\u002Fdbx\u002Ftransactions","en\u002F8.dbx\u002F4.transactions","i-lucide-git-merge",{"title":426,"path":427,"stem":428,"icon":317},"Seeders","\u002Fen\u002Fdbx\u002Fseed","en\u002F8.dbx\u002F5.seed",{"title":430,"icon":55,"path":431,"stem":432,"children":433,"page":44},"paginate","\u002Fen\u002Fpaginate","en\u002F9.paginate",[434,437],{"title":52,"path":435,"stem":436,"icon":55},"\u002Fen\u002Fpaginate\u002Foverview","en\u002F9.paginate\u002F1.overview",{"title":438,"path":439,"stem":440,"icon":19},"Cursor pagination","\u002Fen\u002Fpaginate\u002Fcursor","en\u002F9.paginate\u002F2.cursor",{"id":442,"title":103,"body":443,"description":2701,"extension":2702,"links":2703,"meta":2704,"navigation":2705,"path":104,"seo":2706,"stem":105,"__hash__":2707},"docs_en\u002Fen\u002F12.auth\u002F4.standalone.md",{"type":444,"value":445,"toc":2693},"minimark",[446,471,510,518,543,548,905,909,922,1506,1523,1527,1537,1784,1790,1872,1876,1886,2443,2447,2465,2631,2640,2644,2689],[447,448,449,450,454,455,458,459,462,463,466,467,470],"p",{},"Everything on the previous three pages works without the gpsystem kit, without ",[451,452,453],"code",{},"server.WriteError",", and without any other ",[451,456,457],{},"gp-system\u002F*"," module. This page is the concrete version of that claim: a project whose ",[451,460,461],{},"go.mod"," requires nothing but ",[451,464,465],{},"github.com\u002Fgp-system\u002Fauth",", wiring its own ",[451,468,469],{},"net\u002Fhttp"," middleware, its own role checks, its own policy registry, and its own error rendering by hand.",[472,473,478],"pre",{"className":474,"code":475,"language":476,"meta":477,"style":477},"language-sh shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go mod init example.com\u002Fstandalone-demo\ngo get github.com\u002Fgp-system\u002Fauth\n","sh","",[451,479,480,499],{"__ignoreMap":477},[481,482,485,489,493,496],"span",{"class":483,"line":484},"line",1,[481,486,488],{"class":487},"sBMFI","go",[481,490,492],{"class":491},"sfazB"," mod",[481,494,495],{"class":491}," init",[481,497,498],{"class":491}," example.com\u002Fstandalone-demo\n",[481,500,502,504,507],{"class":483,"line":501},2,[481,503,488],{"class":487},[481,505,506],{"class":491}," get",[481,508,509],{"class":491}," github.com\u002Fgp-system\u002Fauth\n",[472,511,516],{"className":512,"code":514,"language":515},[513],"language-text","require github.com\u002Fgp-system\u002Fauth v0.1.0\n","text",[451,517,514],{"__ignoreMap":477},[447,519,520,521,523,524,523,526,528,529,531,532,534,535,542],{},"No ",[451,522,357],{},", no ",[451,525,385],{},[451,527,404],{},", nothing else: ",[451,530,82],{},"'s own ",[451,533,461],{}," requires only ",[536,537,541],"a",{"href":538,"rel":539},"https:\u002F\u002Fgithub.com\u002Fgolang-jwt\u002Fjwt",[540],"nofollow","golang-jwt\u002Fjwt\u002Fv5",", and that is the only thing that ends up in yours too.",[544,545,547],"h2",{"id":546},"issuing-and-checking-tokens","Issuing and checking tokens",[472,549,552],{"className":550,"code":551,"language":488,"meta":477,"style":477},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","package main\n\nimport (\n    \"time\"\n\n    \"github.com\u002Fgp-system\u002Fauth\"\n)\n\nvar issuer = auth.NewTokenIssuer[auth.DefaultClaims](auth.Config{\n    Secret:    \"dev-secret-change-me\", \u002F\u002F in real code: from env\u002Fsecret manager\n    AccessTTL: 15 * time.Minute,\n    Issuer:    \"standalone-demo\",\n})\n\nfunc issueToken(userID, username string, roles, permissions []string) (string, error) {\n    claims := auth.NewDefaultClaims(auth.Config{Issuer: \"standalone-demo\", AccessTTL: 15 * time.Minute},\n        userID, username, roles, permissions)\n    return issuer.Sign(claims)\n}\n",[451,553,554,563,569,579,591,596,605,611,616,662,686,712,729,735,740,799,858,878,899],{"__ignoreMap":477},[481,555,556,560],{"class":483,"line":484},[481,557,559],{"class":558},"sMK4o","package",[481,561,562],{"class":487}," main\n",[481,564,565],{"class":483,"line":501},[481,566,568],{"emptyLinePlaceholder":567},true,"\n",[481,570,572,576],{"class":483,"line":571},3,[481,573,575],{"class":574},"s7zQu","import",[481,577,578],{"class":558}," (\n",[481,580,582,585,588],{"class":483,"line":581},4,[481,583,584],{"class":558},"    \"",[481,586,587],{"class":487},"time",[481,589,590],{"class":558},"\"\n",[481,592,594],{"class":483,"line":593},5,[481,595,568],{"emptyLinePlaceholder":567},[481,597,599,601,603],{"class":483,"line":598},6,[481,600,584],{"class":558},[481,602,465],{"class":487},[481,604,590],{"class":558},[481,606,608],{"class":483,"line":607},7,[481,609,610],{"class":558},")\n",[481,612,614],{"class":483,"line":613},8,[481,615,568],{"emptyLinePlaceholder":567},[481,617,619,622,626,629,632,635,639,642,644,646,649,652,654,656,659],{"class":483,"line":618},9,[481,620,621],{"class":558},"var",[481,623,625],{"class":624},"sTEyZ"," issuer ",[481,627,628],{"class":558},"=",[481,630,631],{"class":624}," auth",[481,633,634],{"class":558},".",[481,636,638],{"class":637},"s2Zo4","NewTokenIssuer",[481,640,641],{"class":558},"[",[481,643,82],{"class":487},[481,645,634],{"class":558},[481,647,648],{"class":487},"DefaultClaims",[481,650,651],{"class":558},"](",[481,653,82],{"class":487},[481,655,634],{"class":558},[481,657,658],{"class":487},"Config",[481,660,661],{"class":558},"{\n",[481,663,665,668,671,673,676,679,682],{"class":483,"line":664},10,[481,666,667],{"class":624},"    Secret",[481,669,670],{"class":558},":",[481,672,584],{"class":558},[481,674,675],{"class":491},"dev-secret-change-me",[481,677,678],{"class":558},"\"",[481,680,681],{"class":558},",",[481,683,685],{"class":684},"sHwdD"," \u002F\u002F in real code: from env\u002Fsecret manager\n",[481,687,689,692,694,698,701,704,706,709],{"class":483,"line":688},11,[481,690,691],{"class":624},"    AccessTTL",[481,693,670],{"class":558},[481,695,697],{"class":696},"sbssI"," 15",[481,699,700],{"class":558}," *",[481,702,703],{"class":624}," time",[481,705,634],{"class":558},[481,707,708],{"class":624},"Minute",[481,710,711],{"class":558},",\n",[481,713,715,718,720,722,725,727],{"class":483,"line":714},12,[481,716,717],{"class":624},"    Issuer",[481,719,670],{"class":558},[481,721,584],{"class":558},[481,723,724],{"class":491},"standalone-demo",[481,726,678],{"class":558},[481,728,711],{"class":558},[481,730,732],{"class":483,"line":731},13,[481,733,734],{"class":558},"})\n",[481,736,738],{"class":483,"line":737},14,[481,739,568],{"emptyLinePlaceholder":567},[481,741,743,746,749,752,756,758,761,765,767,770,772,775,778,781,784,787,789,791,794,796],{"class":483,"line":742},15,[481,744,745],{"class":558},"func",[481,747,748],{"class":637}," issueToken",[481,750,751],{"class":558},"(",[481,753,755],{"class":754},"sHdIc","userID",[481,757,681],{"class":558},[481,759,760],{"class":754}," username",[481,762,764],{"class":763},"spNyl"," string",[481,766,681],{"class":558},[481,768,769],{"class":754}," roles",[481,771,681],{"class":558},[481,773,774],{"class":754}," permissions",[481,776,777],{"class":558}," []",[481,779,780],{"class":763},"string",[481,782,783],{"class":558},")",[481,785,786],{"class":558}," (",[481,788,780],{"class":763},[481,790,681],{"class":558},[481,792,793],{"class":763}," error",[481,795,783],{"class":558},[481,797,798],{"class":558}," {\n",[481,800,802,805,808,810,812,815,817,819,821,823,826,829,831,834,836,838,840,843,845,847,849,851,853,855],{"class":483,"line":801},16,[481,803,804],{"class":624},"    claims ",[481,806,807],{"class":558},":=",[481,809,631],{"class":624},[481,811,634],{"class":558},[481,813,814],{"class":637},"NewDefaultClaims",[481,816,751],{"class":558},[481,818,82],{"class":487},[481,820,634],{"class":558},[481,822,658],{"class":487},[481,824,825],{"class":558},"{",[481,827,828],{"class":624},"Issuer",[481,830,670],{"class":558},[481,832,833],{"class":558}," \"",[481,835,724],{"class":491},[481,837,678],{"class":558},[481,839,681],{"class":558},[481,841,842],{"class":624}," AccessTTL",[481,844,670],{"class":558},[481,846,697],{"class":696},[481,848,700],{"class":558},[481,850,703],{"class":624},[481,852,634],{"class":558},[481,854,708],{"class":624},[481,856,857],{"class":558},"},\n",[481,859,861,864,866,868,870,872,874,876],{"class":483,"line":860},17,[481,862,863],{"class":624},"        userID",[481,865,681],{"class":558},[481,867,760],{"class":624},[481,869,681],{"class":558},[481,871,769],{"class":624},[481,873,681],{"class":558},[481,875,774],{"class":624},[481,877,610],{"class":558},[481,879,881,884,887,889,892,894,897],{"class":483,"line":880},18,[481,882,883],{"class":574},"    return",[481,885,886],{"class":624}," issuer",[481,888,634],{"class":558},[481,890,891],{"class":637},"Sign",[481,893,751],{"class":558},[481,895,896],{"class":624},"claims",[481,898,610],{"class":558},[481,900,902],{"class":483,"line":901},19,[481,903,904],{"class":558},"}\n",[544,906,908],{"id":907},"a-hand-written-nethttp-middleware","A hand-written net\u002Fhttp middleware",[447,910,911,914,915,918,919,921],{},[451,912,913],{},"Identify"," does the parsing and identity mapping; you decide what happens on failure. Here we write a small JSON body ourselves, with ",[451,916,917],{},"errors.Is"," picking the status code, no ",[451,920,385],{}," involved:",[472,923,925],{"className":550,"code":924,"language":488,"meta":477,"style":477},"package main\n\nimport (\n    \"encoding\u002Fjson\"\n    \"errors\"\n    \"net\u002Fhttp\"\n\n    \"github.com\u002Fgp-system\u002Fauth\"\n    \"github.com\u002Fgp-system\u002Fauth\u002Frbac\"\n)\n\nfunc requireAuth(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        id, err := auth.Identify(issuer, auth.DefaultIdentity, r.Header.Get(\"Authorization\"))\n        if err != nil {\n            writeAuthError(w, err)\n            return\n        }\n        next.ServeHTTP(w, r.WithContext(rbac.WithIdentity(r.Context(), id)))\n    })\n}\n\nfunc writeAuthError(w http.ResponseWriter, err error) {\n    status := http.StatusInternalServerError\n    switch {\n    case errors.Is(err, auth.ErrMissingToken),\n        errors.Is(err, auth.ErrInvalidToken),\n        errors.Is(err, auth.ErrInvalidClaims):\n        status = http.StatusUnauthorized\n    }\n    w.Header().Set(\"Content-Type\", \"application\u002Fjson\")\n    w.WriteHeader(status)\n    _ = json.NewEncoder(w).Encode(map[string]string{\"error\": err.Error()})\n}\n",[451,926,927,933,937,943,952,961,969,973,981,990,994,998,1028,1071,1129,1144,1160,1165,1170,1222,1228,1233,1238,1266,1281,1289,1320,1345,1370,1385,1391,1427,1444,1501],{"__ignoreMap":477},[481,928,929,931],{"class":483,"line":484},[481,930,559],{"class":558},[481,932,562],{"class":487},[481,934,935],{"class":483,"line":501},[481,936,568],{"emptyLinePlaceholder":567},[481,938,939,941],{"class":483,"line":571},[481,940,575],{"class":574},[481,942,578],{"class":558},[481,944,945,947,950],{"class":483,"line":581},[481,946,584],{"class":558},[481,948,949],{"class":487},"encoding\u002Fjson",[481,951,590],{"class":558},[481,953,954,956,959],{"class":483,"line":593},[481,955,584],{"class":558},[481,957,958],{"class":487},"errors",[481,960,590],{"class":558},[481,962,963,965,967],{"class":483,"line":598},[481,964,584],{"class":558},[481,966,469],{"class":487},[481,968,590],{"class":558},[481,970,971],{"class":483,"line":607},[481,972,568],{"emptyLinePlaceholder":567},[481,974,975,977,979],{"class":483,"line":613},[481,976,584],{"class":558},[481,978,465],{"class":487},[481,980,590],{"class":558},[481,982,983,985,988],{"class":483,"line":618},[481,984,584],{"class":558},[481,986,987],{"class":487},"github.com\u002Fgp-system\u002Fauth\u002Frbac",[481,989,590],{"class":558},[481,991,992],{"class":483,"line":664},[481,993,610],{"class":558},[481,995,996],{"class":483,"line":688},[481,997,568],{"emptyLinePlaceholder":567},[481,999,1000,1002,1005,1007,1010,1013,1015,1018,1020,1022,1024,1026],{"class":483,"line":714},[481,1001,745],{"class":558},[481,1003,1004],{"class":637}," requireAuth",[481,1006,751],{"class":558},[481,1008,1009],{"class":754},"next",[481,1011,1012],{"class":487}," http",[481,1014,634],{"class":558},[481,1016,1017],{"class":487},"Handler",[481,1019,783],{"class":558},[481,1021,1012],{"class":487},[481,1023,634],{"class":558},[481,1025,1017],{"class":487},[481,1027,798],{"class":558},[481,1029,1030,1032,1034,1036,1039,1042,1045,1047,1049,1052,1054,1057,1059,1062,1064,1067,1069],{"class":483,"line":731},[481,1031,883],{"class":574},[481,1033,1012],{"class":624},[481,1035,634],{"class":558},[481,1037,1038],{"class":637},"HandlerFunc",[481,1040,1041],{"class":558},"(func(",[481,1043,1044],{"class":754},"w",[481,1046,1012],{"class":487},[481,1048,634],{"class":558},[481,1050,1051],{"class":487},"ResponseWriter",[481,1053,681],{"class":558},[481,1055,1056],{"class":754}," r",[481,1058,700],{"class":558},[481,1060,1061],{"class":487},"http",[481,1063,634],{"class":558},[481,1065,1066],{"class":487},"Request",[481,1068,783],{"class":558},[481,1070,798],{"class":558},[481,1072,1073,1076,1078,1081,1083,1085,1087,1089,1091,1094,1096,1098,1100,1103,1105,1107,1109,1112,1114,1117,1119,1121,1124,1126],{"class":483,"line":737},[481,1074,1075],{"class":624},"        id",[481,1077,681],{"class":558},[481,1079,1080],{"class":624}," err ",[481,1082,807],{"class":558},[481,1084,631],{"class":624},[481,1086,634],{"class":558},[481,1088,913],{"class":637},[481,1090,751],{"class":558},[481,1092,1093],{"class":624},"issuer",[481,1095,681],{"class":558},[481,1097,631],{"class":624},[481,1099,634],{"class":558},[481,1101,1102],{"class":624},"DefaultIdentity",[481,1104,681],{"class":558},[481,1106,1056],{"class":624},[481,1108,634],{"class":558},[481,1110,1111],{"class":624},"Header",[481,1113,634],{"class":558},[481,1115,1116],{"class":637},"Get",[481,1118,751],{"class":558},[481,1120,678],{"class":558},[481,1122,1123],{"class":491},"Authorization",[481,1125,678],{"class":558},[481,1127,1128],{"class":558},"))\n",[481,1130,1131,1134,1136,1139,1142],{"class":483,"line":742},[481,1132,1133],{"class":574},"        if",[481,1135,1080],{"class":624},[481,1137,1138],{"class":558},"!=",[481,1140,1141],{"class":558}," nil",[481,1143,798],{"class":558},[481,1145,1146,1149,1151,1153,1155,1158],{"class":483,"line":801},[481,1147,1148],{"class":637},"            writeAuthError",[481,1150,751],{"class":558},[481,1152,1044],{"class":624},[481,1154,681],{"class":558},[481,1156,1157],{"class":624}," err",[481,1159,610],{"class":558},[481,1161,1162],{"class":483,"line":860},[481,1163,1164],{"class":574},"            return\n",[481,1166,1167],{"class":483,"line":880},[481,1168,1169],{"class":558},"        }\n",[481,1171,1172,1175,1177,1180,1182,1184,1186,1188,1190,1193,1195,1198,1200,1203,1205,1208,1210,1213,1216,1219],{"class":483,"line":901},[481,1173,1174],{"class":624},"        next",[481,1176,634],{"class":558},[481,1178,1179],{"class":637},"ServeHTTP",[481,1181,751],{"class":558},[481,1183,1044],{"class":624},[481,1185,681],{"class":558},[481,1187,1056],{"class":624},[481,1189,634],{"class":558},[481,1191,1192],{"class":637},"WithContext",[481,1194,751],{"class":558},[481,1196,1197],{"class":624},"rbac",[481,1199,634],{"class":558},[481,1201,1202],{"class":637},"WithIdentity",[481,1204,751],{"class":558},[481,1206,1207],{"class":624},"r",[481,1209,634],{"class":558},[481,1211,1212],{"class":637},"Context",[481,1214,1215],{"class":558},"(),",[481,1217,1218],{"class":624}," id",[481,1220,1221],{"class":558},")))\n",[481,1223,1225],{"class":483,"line":1224},20,[481,1226,1227],{"class":558},"    })\n",[481,1229,1231],{"class":483,"line":1230},21,[481,1232,904],{"class":558},[481,1234,1236],{"class":483,"line":1235},22,[481,1237,568],{"emptyLinePlaceholder":567},[481,1239,1241,1243,1246,1248,1250,1252,1254,1256,1258,1260,1262,1264],{"class":483,"line":1240},23,[481,1242,745],{"class":558},[481,1244,1245],{"class":637}," writeAuthError",[481,1247,751],{"class":558},[481,1249,1044],{"class":754},[481,1251,1012],{"class":487},[481,1253,634],{"class":558},[481,1255,1051],{"class":487},[481,1257,681],{"class":558},[481,1259,1157],{"class":754},[481,1261,793],{"class":763},[481,1263,783],{"class":558},[481,1265,798],{"class":558},[481,1267,1269,1272,1274,1276,1278],{"class":483,"line":1268},24,[481,1270,1271],{"class":624},"    status ",[481,1273,807],{"class":558},[481,1275,1012],{"class":624},[481,1277,634],{"class":558},[481,1279,1280],{"class":624},"StatusInternalServerError\n",[481,1282,1284,1287],{"class":483,"line":1283},25,[481,1285,1286],{"class":574},"    switch",[481,1288,798],{"class":558},[481,1290,1292,1295,1298,1300,1303,1305,1308,1310,1312,1314,1317],{"class":483,"line":1291},26,[481,1293,1294],{"class":574},"    case",[481,1296,1297],{"class":624}," errors",[481,1299,634],{"class":558},[481,1301,1302],{"class":637},"Is",[481,1304,751],{"class":558},[481,1306,1307],{"class":624},"err",[481,1309,681],{"class":558},[481,1311,631],{"class":624},[481,1313,634],{"class":558},[481,1315,1316],{"class":624},"ErrMissingToken",[481,1318,1319],{"class":558},"),\n",[481,1321,1323,1326,1328,1330,1332,1334,1336,1338,1340,1343],{"class":483,"line":1322},27,[481,1324,1325],{"class":624},"        errors",[481,1327,634],{"class":558},[481,1329,1302],{"class":637},[481,1331,751],{"class":558},[481,1333,1307],{"class":624},[481,1335,681],{"class":558},[481,1337,631],{"class":624},[481,1339,634],{"class":558},[481,1341,1342],{"class":624},"ErrInvalidToken",[481,1344,1319],{"class":558},[481,1346,1348,1350,1352,1354,1356,1358,1360,1362,1364,1367],{"class":483,"line":1347},28,[481,1349,1325],{"class":624},[481,1351,634],{"class":558},[481,1353,1302],{"class":637},[481,1355,751],{"class":558},[481,1357,1307],{"class":624},[481,1359,681],{"class":558},[481,1361,631],{"class":624},[481,1363,634],{"class":558},[481,1365,1366],{"class":624},"ErrInvalidClaims",[481,1368,1369],{"class":558},"):\n",[481,1371,1373,1376,1378,1380,1382],{"class":483,"line":1372},29,[481,1374,1375],{"class":624},"        status ",[481,1377,628],{"class":558},[481,1379,1012],{"class":624},[481,1381,634],{"class":558},[481,1383,1384],{"class":624},"StatusUnauthorized\n",[481,1386,1388],{"class":483,"line":1387},30,[481,1389,1390],{"class":558},"    }\n",[481,1392,1394,1397,1399,1401,1404,1407,1409,1411,1414,1416,1418,1420,1423,1425],{"class":483,"line":1393},31,[481,1395,1396],{"class":624},"    w",[481,1398,634],{"class":558},[481,1400,1111],{"class":637},[481,1402,1403],{"class":558},"().",[481,1405,1406],{"class":637},"Set",[481,1408,751],{"class":558},[481,1410,678],{"class":558},[481,1412,1413],{"class":491},"Content-Type",[481,1415,678],{"class":558},[481,1417,681],{"class":558},[481,1419,833],{"class":558},[481,1421,1422],{"class":491},"application\u002Fjson",[481,1424,678],{"class":558},[481,1426,610],{"class":558},[481,1428,1430,1432,1434,1437,1439,1442],{"class":483,"line":1429},32,[481,1431,1396],{"class":624},[481,1433,634],{"class":558},[481,1435,1436],{"class":637},"WriteHeader",[481,1438,751],{"class":558},[481,1440,1441],{"class":624},"status",[481,1443,610],{"class":558},[481,1445,1447,1450,1452,1455,1457,1460,1462,1464,1467,1470,1473,1475,1478,1480,1482,1484,1487,1489,1491,1493,1495,1498],{"class":483,"line":1446},33,[481,1448,1449],{"class":624},"    _ ",[481,1451,628],{"class":558},[481,1453,1454],{"class":624}," json",[481,1456,634],{"class":558},[481,1458,1459],{"class":637},"NewEncoder",[481,1461,751],{"class":558},[481,1463,1044],{"class":624},[481,1465,1466],{"class":558},").",[481,1468,1469],{"class":637},"Encode",[481,1471,1472],{"class":558},"(map[",[481,1474,780],{"class":763},[481,1476,1477],{"class":558},"]",[481,1479,780],{"class":763},[481,1481,825],{"class":558},[481,1483,678],{"class":558},[481,1485,1486],{"class":491},"error",[481,1488,678],{"class":558},[481,1490,670],{"class":558},[481,1492,1157],{"class":624},[481,1494,634],{"class":558},[481,1496,1497],{"class":637},"Error",[481,1499,1500],{"class":558},"()})\n",[481,1502,1504],{"class":483,"line":1503},34,[481,1505,904],{"class":558},[447,1507,1508,1509,1512,1513,1516,1517,1519,1520,1522],{},"This is not the only shape: a middleware could just as well render ",[451,1510,1511],{},"text\u002Fplain"," with ",[451,1514,1515],{},"http.Error",", or reuse an error-handling convention your codebase already has. ",[451,1518,82],{}," has no opinion; it only guarantees ",[451,1521,917],{}," works against the three sentinels.",[544,1524,1526],{"id":1525},"role-checks-without-a-middleware","Role checks without a middleware",[447,1528,1529,1532,1533,1536],{},[451,1530,1531],{},"rbac.CheckRole","\u002F",[451,1534,1535],{},"CheckPermission"," are plain functions; you can call them directly in a handler, no guard middleware required:",[472,1538,1540],{"className":550,"code":1539,"language":488,"meta":477,"style":477},"func adminOnly(next http.HandlerFunc) http.HandlerFunc {\n    return func(w http.ResponseWriter, r *http.Request) {\n        id := rbac.FromContext(r.Context())\n        if err := rbac.CheckRole(id, \"admin\"); err != nil {\n            status := http.StatusForbidden\n            if errors.Is(err, rbac.ErrUnauthenticated) {\n                status = http.StatusUnauthorized\n            }\n            http.Error(w, err.Error(), status)\n            return\n        }\n        next(w, r)\n    }\n}\n",[451,1541,1542,1569,1600,1626,1666,1680,1708,1721,1726,1754,1758,1762,1776,1780],{"__ignoreMap":477},[481,1543,1544,1546,1549,1551,1553,1555,1557,1559,1561,1563,1565,1567],{"class":483,"line":484},[481,1545,745],{"class":558},[481,1547,1548],{"class":637}," adminOnly",[481,1550,751],{"class":558},[481,1552,1009],{"class":754},[481,1554,1012],{"class":487},[481,1556,634],{"class":558},[481,1558,1038],{"class":487},[481,1560,783],{"class":558},[481,1562,1012],{"class":487},[481,1564,634],{"class":558},[481,1566,1038],{"class":487},[481,1568,798],{"class":558},[481,1570,1571,1573,1576,1578,1580,1582,1584,1586,1588,1590,1592,1594,1596,1598],{"class":483,"line":501},[481,1572,883],{"class":574},[481,1574,1575],{"class":558}," func(",[481,1577,1044],{"class":754},[481,1579,1012],{"class":487},[481,1581,634],{"class":558},[481,1583,1051],{"class":487},[481,1585,681],{"class":558},[481,1587,1056],{"class":754},[481,1589,700],{"class":558},[481,1591,1061],{"class":487},[481,1593,634],{"class":558},[481,1595,1066],{"class":487},[481,1597,783],{"class":558},[481,1599,798],{"class":558},[481,1601,1602,1605,1607,1610,1612,1615,1617,1619,1621,1623],{"class":483,"line":571},[481,1603,1604],{"class":624},"        id ",[481,1606,807],{"class":558},[481,1608,1609],{"class":624}," rbac",[481,1611,634],{"class":558},[481,1613,1614],{"class":637},"FromContext",[481,1616,751],{"class":558},[481,1618,1207],{"class":624},[481,1620,634],{"class":558},[481,1622,1212],{"class":637},[481,1624,1625],{"class":558},"())\n",[481,1627,1628,1630,1632,1634,1636,1638,1641,1643,1646,1648,1650,1653,1655,1658,1660,1662,1664],{"class":483,"line":581},[481,1629,1133],{"class":574},[481,1631,1080],{"class":624},[481,1633,807],{"class":558},[481,1635,1609],{"class":624},[481,1637,634],{"class":558},[481,1639,1640],{"class":637},"CheckRole",[481,1642,751],{"class":558},[481,1644,1645],{"class":624},"id",[481,1647,681],{"class":558},[481,1649,833],{"class":558},[481,1651,1652],{"class":491},"admin",[481,1654,678],{"class":558},[481,1656,1657],{"class":558},");",[481,1659,1080],{"class":624},[481,1661,1138],{"class":558},[481,1663,1141],{"class":558},[481,1665,798],{"class":558},[481,1667,1668,1671,1673,1675,1677],{"class":483,"line":593},[481,1669,1670],{"class":624},"            status ",[481,1672,807],{"class":558},[481,1674,1012],{"class":624},[481,1676,634],{"class":558},[481,1678,1679],{"class":624},"StatusForbidden\n",[481,1681,1682,1685,1687,1689,1691,1693,1695,1697,1699,1701,1704,1706],{"class":483,"line":598},[481,1683,1684],{"class":574},"            if",[481,1686,1297],{"class":624},[481,1688,634],{"class":558},[481,1690,1302],{"class":637},[481,1692,751],{"class":558},[481,1694,1307],{"class":624},[481,1696,681],{"class":558},[481,1698,1609],{"class":624},[481,1700,634],{"class":558},[481,1702,1703],{"class":624},"ErrUnauthenticated",[481,1705,783],{"class":558},[481,1707,798],{"class":558},[481,1709,1710,1713,1715,1717,1719],{"class":483,"line":607},[481,1711,1712],{"class":624},"                status ",[481,1714,628],{"class":558},[481,1716,1012],{"class":624},[481,1718,634],{"class":558},[481,1720,1384],{"class":624},[481,1722,1723],{"class":483,"line":613},[481,1724,1725],{"class":558},"            }\n",[481,1727,1728,1731,1733,1735,1737,1739,1741,1743,1745,1747,1749,1752],{"class":483,"line":618},[481,1729,1730],{"class":624},"            http",[481,1732,634],{"class":558},[481,1734,1497],{"class":637},[481,1736,751],{"class":558},[481,1738,1044],{"class":624},[481,1740,681],{"class":558},[481,1742,1157],{"class":624},[481,1744,634],{"class":558},[481,1746,1497],{"class":637},[481,1748,1215],{"class":558},[481,1750,1751],{"class":624}," status",[481,1753,610],{"class":558},[481,1755,1756],{"class":483,"line":664},[481,1757,1164],{"class":574},[481,1759,1760],{"class":483,"line":688},[481,1761,1169],{"class":558},[481,1763,1764,1766,1768,1770,1772,1774],{"class":483,"line":714},[481,1765,1174],{"class":637},[481,1767,751],{"class":558},[481,1769,1044],{"class":624},[481,1771,681],{"class":558},[481,1773,1056],{"class":624},[481,1775,610],{"class":558},[481,1777,1778],{"class":483,"line":731},[481,1779,1390],{"class":558},[481,1781,1782],{"class":483,"line":737},[481,1783,904],{"class":558},[447,1785,1786,1787,670],{},"Wire it after ",[451,1788,1789],{},"requireAuth",[472,1791,1793],{"className":550,"code":1792,"language":488,"meta":477,"style":477},"mux := http.NewServeMux()\nmux.Handle(\"\u002Fadmin\u002Freports\", requireAuth(adminOnly(handleReports)))\nhttp.ListenAndServe(\":8080\", mux)\n",[451,1794,1795,1812,1847],{"__ignoreMap":477},[481,1796,1797,1800,1802,1804,1806,1809],{"class":483,"line":484},[481,1798,1799],{"class":624},"mux ",[481,1801,807],{"class":558},[481,1803,1012],{"class":624},[481,1805,634],{"class":558},[481,1807,1808],{"class":637},"NewServeMux",[481,1810,1811],{"class":558},"()\n",[481,1813,1814,1817,1819,1822,1824,1826,1829,1831,1833,1835,1837,1840,1842,1845],{"class":483,"line":501},[481,1815,1816],{"class":624},"mux",[481,1818,634],{"class":558},[481,1820,1821],{"class":637},"Handle",[481,1823,751],{"class":558},[481,1825,678],{"class":558},[481,1827,1828],{"class":491},"\u002Fadmin\u002Freports",[481,1830,678],{"class":558},[481,1832,681],{"class":558},[481,1834,1004],{"class":637},[481,1836,751],{"class":558},[481,1838,1839],{"class":637},"adminOnly",[481,1841,751],{"class":558},[481,1843,1844],{"class":624},"handleReports",[481,1846,1221],{"class":558},[481,1848,1849,1851,1853,1856,1858,1860,1863,1865,1867,1870],{"class":483,"line":571},[481,1850,1061],{"class":624},[481,1852,634],{"class":558},[481,1854,1855],{"class":637},"ListenAndServe",[481,1857,751],{"class":558},[481,1859,678],{"class":558},[481,1861,1862],{"class":491},":8080",[481,1864,678],{"class":558},[481,1866,681],{"class":558},[481,1868,1869],{"class":624}," mux",[481,1871,610],{"class":558},[544,1873,1875],{"id":1874},"a-policy-registry-wired-by-hand","A policy registry, wired by hand",[447,1877,1878,1881,1882,1885],{},[451,1879,1880],{},"policy.Enforcer"," targets oapi-codegen's strict-server shape, but nothing stops you from calling a ",[451,1883,1884],{},"Registry"," directly for a hand-rolled endpoint:",[472,1887,1889],{"className":550,"code":1888,"language":488,"meta":477,"style":477},"package main\n\nimport (\n    \"context\"\n    \"errors\"\n    \"net\u002Fhttp\"\n\n    \"github.com\u002Fgp-system\u002Fauth\u002Fpolicy\"\n    \"github.com\u002Fgp-system\u002Fauth\u002Frbac\"\n)\n\nvar orderPolicies = func() *policy.Registry {\n    reg := policy.NewRegistry()\n    reg.Register(\"orders.view\", func(_ context.Context, id *rbac.Identity, req any) error {\n        orderOwnerID := req.(string) \u002F\u002F in real code: looked up from the order record\n        if id.HasRole(\"admin\") || orderOwnerID == id.Subject {\n            return nil\n        }\n        return policy.Deny(\"you can only view your own orders\")\n    })\n    return reg\n}()\n\nfunc handleGetOrder(w http.ResponseWriter, r *http.Request) {\n    id := rbac.FromContext(r.Context())\n    fn, _ := orderPolicies.Get(\"orders.view\")\n    if err := fn(r.Context(), id, lookupOrderOwner(r)); err != nil {\n        var d *policy.Denial\n        if errors.As(err, &d) {\n            http.Error(w, d.Detail, http.StatusForbidden)\n            return\n        }\n        http.Error(w, \"internal error\", http.StatusInternalServerError)\n        return\n    }\n    \u002F\u002F ... write the order\n}\n",[451,1890,1891,1897,1901,1907,1916,1924,1932,1936,1945,1953,1957,1961,1984,2001,2061,2080,2119,2127,2131,2154,2158,2165,2170,2174,2207,2230,2259,2303,2321,2348,2381,2385,2389,2422,2427,2432,2438],{"__ignoreMap":477},[481,1892,1893,1895],{"class":483,"line":484},[481,1894,559],{"class":558},[481,1896,562],{"class":487},[481,1898,1899],{"class":483,"line":501},[481,1900,568],{"emptyLinePlaceholder":567},[481,1902,1903,1905],{"class":483,"line":571},[481,1904,575],{"class":574},[481,1906,578],{"class":558},[481,1908,1909,1911,1914],{"class":483,"line":581},[481,1910,584],{"class":558},[481,1912,1913],{"class":487},"context",[481,1915,590],{"class":558},[481,1917,1918,1920,1922],{"class":483,"line":593},[481,1919,584],{"class":558},[481,1921,958],{"class":487},[481,1923,590],{"class":558},[481,1925,1926,1928,1930],{"class":483,"line":598},[481,1927,584],{"class":558},[481,1929,469],{"class":487},[481,1931,590],{"class":558},[481,1933,1934],{"class":483,"line":607},[481,1935,568],{"emptyLinePlaceholder":567},[481,1937,1938,1940,1943],{"class":483,"line":613},[481,1939,584],{"class":558},[481,1941,1942],{"class":487},"github.com\u002Fgp-system\u002Fauth\u002Fpolicy",[481,1944,590],{"class":558},[481,1946,1947,1949,1951],{"class":483,"line":618},[481,1948,584],{"class":558},[481,1950,987],{"class":487},[481,1952,590],{"class":558},[481,1954,1955],{"class":483,"line":664},[481,1956,610],{"class":558},[481,1958,1959],{"class":483,"line":688},[481,1960,568],{"emptyLinePlaceholder":567},[481,1962,1963,1965,1968,1970,1973,1975,1978,1980,1982],{"class":483,"line":714},[481,1964,621],{"class":558},[481,1966,1967],{"class":624}," orderPolicies ",[481,1969,628],{"class":558},[481,1971,1972],{"class":558}," func()",[481,1974,700],{"class":558},[481,1976,1977],{"class":487},"policy",[481,1979,634],{"class":558},[481,1981,1884],{"class":487},[481,1983,798],{"class":558},[481,1985,1986,1989,1991,1994,1996,1999],{"class":483,"line":731},[481,1987,1988],{"class":624},"    reg ",[481,1990,807],{"class":558},[481,1992,1993],{"class":624}," policy",[481,1995,634],{"class":558},[481,1997,1998],{"class":637},"NewRegistry",[481,2000,1811],{"class":558},[481,2002,2003,2006,2008,2011,2013,2015,2018,2020,2022,2024,2027,2030,2032,2034,2036,2038,2040,2042,2044,2047,2049,2052,2055,2057,2059],{"class":483,"line":737},[481,2004,2005],{"class":624},"    reg",[481,2007,634],{"class":558},[481,2009,2010],{"class":637},"Register",[481,2012,751],{"class":558},[481,2014,678],{"class":558},[481,2016,2017],{"class":491},"orders.view",[481,2019,678],{"class":558},[481,2021,681],{"class":558},[481,2023,1575],{"class":558},[481,2025,2026],{"class":754},"_",[481,2028,2029],{"class":487}," context",[481,2031,634],{"class":558},[481,2033,1212],{"class":487},[481,2035,681],{"class":558},[481,2037,1218],{"class":754},[481,2039,700],{"class":558},[481,2041,1197],{"class":487},[481,2043,634],{"class":558},[481,2045,2046],{"class":487},"Identity",[481,2048,681],{"class":558},[481,2050,2051],{"class":754}," req",[481,2053,2054],{"class":487}," any",[481,2056,783],{"class":558},[481,2058,793],{"class":763},[481,2060,798],{"class":558},[481,2062,2063,2066,2068,2070,2073,2075,2077],{"class":483,"line":742},[481,2064,2065],{"class":624},"        orderOwnerID ",[481,2067,807],{"class":558},[481,2069,2051],{"class":624},[481,2071,2072],{"class":558},".(",[481,2074,780],{"class":763},[481,2076,783],{"class":558},[481,2078,2079],{"class":684}," \u002F\u002F in real code: looked up from the order record\n",[481,2081,2082,2084,2086,2088,2091,2093,2095,2097,2099,2101,2104,2107,2110,2112,2114,2117],{"class":483,"line":801},[481,2083,1133],{"class":574},[481,2085,1218],{"class":624},[481,2087,634],{"class":558},[481,2089,2090],{"class":637},"HasRole",[481,2092,751],{"class":558},[481,2094,678],{"class":558},[481,2096,1652],{"class":491},[481,2098,678],{"class":558},[481,2100,783],{"class":558},[481,2102,2103],{"class":558}," ||",[481,2105,2106],{"class":624}," orderOwnerID ",[481,2108,2109],{"class":558},"==",[481,2111,1218],{"class":624},[481,2113,634],{"class":558},[481,2115,2116],{"class":624},"Subject ",[481,2118,661],{"class":558},[481,2120,2121,2124],{"class":483,"line":860},[481,2122,2123],{"class":574},"            return",[481,2125,2126],{"class":558}," nil\n",[481,2128,2129],{"class":483,"line":880},[481,2130,1169],{"class":558},[481,2132,2133,2136,2138,2140,2143,2145,2147,2150,2152],{"class":483,"line":901},[481,2134,2135],{"class":574},"        return",[481,2137,1993],{"class":624},[481,2139,634],{"class":558},[481,2141,2142],{"class":637},"Deny",[481,2144,751],{"class":558},[481,2146,678],{"class":558},[481,2148,2149],{"class":491},"you can only view your own orders",[481,2151,678],{"class":558},[481,2153,610],{"class":558},[481,2155,2156],{"class":483,"line":1224},[481,2157,1227],{"class":558},[481,2159,2160,2162],{"class":483,"line":1230},[481,2161,883],{"class":574},[481,2163,2164],{"class":624}," reg\n",[481,2166,2167],{"class":483,"line":1235},[481,2168,2169],{"class":558},"}()\n",[481,2171,2172],{"class":483,"line":1240},[481,2173,568],{"emptyLinePlaceholder":567},[481,2175,2176,2178,2181,2183,2185,2187,2189,2191,2193,2195,2197,2199,2201,2203,2205],{"class":483,"line":1268},[481,2177,745],{"class":558},[481,2179,2180],{"class":637}," handleGetOrder",[481,2182,751],{"class":558},[481,2184,1044],{"class":754},[481,2186,1012],{"class":487},[481,2188,634],{"class":558},[481,2190,1051],{"class":487},[481,2192,681],{"class":558},[481,2194,1056],{"class":754},[481,2196,700],{"class":558},[481,2198,1061],{"class":487},[481,2200,634],{"class":558},[481,2202,1066],{"class":487},[481,2204,783],{"class":558},[481,2206,798],{"class":558},[481,2208,2209,2212,2214,2216,2218,2220,2222,2224,2226,2228],{"class":483,"line":1283},[481,2210,2211],{"class":624},"    id ",[481,2213,807],{"class":558},[481,2215,1609],{"class":624},[481,2217,634],{"class":558},[481,2219,1614],{"class":637},[481,2221,751],{"class":558},[481,2223,1207],{"class":624},[481,2225,634],{"class":558},[481,2227,1212],{"class":637},[481,2229,1625],{"class":558},[481,2231,2232,2235,2237,2240,2242,2245,2247,2249,2251,2253,2255,2257],{"class":483,"line":1291},[481,2233,2234],{"class":624},"    fn",[481,2236,681],{"class":558},[481,2238,2239],{"class":624}," _ ",[481,2241,807],{"class":558},[481,2243,2244],{"class":624}," orderPolicies",[481,2246,634],{"class":558},[481,2248,1116],{"class":637},[481,2250,751],{"class":558},[481,2252,678],{"class":558},[481,2254,2017],{"class":491},[481,2256,678],{"class":558},[481,2258,610],{"class":558},[481,2260,2261,2264,2266,2268,2271,2273,2275,2277,2279,2281,2283,2285,2288,2290,2292,2295,2297,2299,2301],{"class":483,"line":1322},[481,2262,2263],{"class":574},"    if",[481,2265,1080],{"class":624},[481,2267,807],{"class":558},[481,2269,2270],{"class":637}," fn",[481,2272,751],{"class":558},[481,2274,1207],{"class":624},[481,2276,634],{"class":558},[481,2278,1212],{"class":637},[481,2280,1215],{"class":558},[481,2282,1218],{"class":624},[481,2284,681],{"class":558},[481,2286,2287],{"class":637}," lookupOrderOwner",[481,2289,751],{"class":558},[481,2291,1207],{"class":624},[481,2293,2294],{"class":558},"));",[481,2296,1080],{"class":624},[481,2298,1138],{"class":558},[481,2300,1141],{"class":558},[481,2302,798],{"class":558},[481,2304,2305,2308,2311,2314,2316,2318],{"class":483,"line":1347},[481,2306,2307],{"class":558},"        var",[481,2309,2310],{"class":624}," d ",[481,2312,2313],{"class":558},"*",[481,2315,1977],{"class":487},[481,2317,634],{"class":558},[481,2319,2320],{"class":487},"Denial\n",[481,2322,2323,2325,2327,2329,2332,2334,2336,2338,2341,2344,2346],{"class":483,"line":1372},[481,2324,1133],{"class":574},[481,2326,1297],{"class":624},[481,2328,634],{"class":558},[481,2330,2331],{"class":637},"As",[481,2333,751],{"class":558},[481,2335,1307],{"class":624},[481,2337,681],{"class":558},[481,2339,2340],{"class":558}," &",[481,2342,2343],{"class":624},"d",[481,2345,783],{"class":558},[481,2347,798],{"class":558},[481,2349,2350,2352,2354,2356,2358,2360,2362,2365,2367,2370,2372,2374,2376,2379],{"class":483,"line":1387},[481,2351,1730],{"class":624},[481,2353,634],{"class":558},[481,2355,1497],{"class":637},[481,2357,751],{"class":558},[481,2359,1044],{"class":624},[481,2361,681],{"class":558},[481,2363,2364],{"class":624}," d",[481,2366,634],{"class":558},[481,2368,2369],{"class":624},"Detail",[481,2371,681],{"class":558},[481,2373,1012],{"class":624},[481,2375,634],{"class":558},[481,2377,2378],{"class":624},"StatusForbidden",[481,2380,610],{"class":558},[481,2382,2383],{"class":483,"line":1393},[481,2384,1164],{"class":574},[481,2386,2387],{"class":483,"line":1429},[481,2388,1169],{"class":558},[481,2390,2391,2394,2396,2398,2400,2402,2404,2406,2409,2411,2413,2415,2417,2420],{"class":483,"line":1446},[481,2392,2393],{"class":624},"        http",[481,2395,634],{"class":558},[481,2397,1497],{"class":637},[481,2399,751],{"class":558},[481,2401,1044],{"class":624},[481,2403,681],{"class":558},[481,2405,833],{"class":558},[481,2407,2408],{"class":491},"internal error",[481,2410,678],{"class":558},[481,2412,681],{"class":558},[481,2414,1012],{"class":624},[481,2416,634],{"class":558},[481,2418,2419],{"class":624},"StatusInternalServerError",[481,2421,610],{"class":558},[481,2423,2424],{"class":483,"line":1503},[481,2425,2426],{"class":574},"        return\n",[481,2428,2430],{"class":483,"line":2429},35,[481,2431,1390],{"class":558},[481,2433,2435],{"class":483,"line":2434},36,[481,2436,2437],{"class":684},"    \u002F\u002F ... write the order\n",[481,2439,2441],{"class":483,"line":2440},37,[481,2442,904],{"class":558},[544,2444,2446],{"id":2445},"no-http-at-all-a-websocket-handshake","No HTTP at all: a WebSocket handshake",[447,2448,2449,2451,2452,2456,2457,2460,2461,2464],{},[451,2450,913],{}," takes a header ",[2453,2454,2455],"strong",{},"value",", not a ",[451,2458,2459],{},"*http.Request",", so the same call works wherever a bearer token shows up: a WebSocket handshake's ",[451,2462,2463],{},"Sec-WebSocket-Protocol"," trick, a first message on the socket, gRPC metadata, anything.",[472,2466,2468],{"className":550,"code":2467,"language":488,"meta":477,"style":477},"func acceptConnection(authorizationValue string) (*rbac.Identity, error) {\n    id, err := auth.Identify(issuer, auth.DefaultIdentity, authorizationValue)\n    if err != nil {\n        return nil, err \u002F\u002F caller decides how to close the connection \u002F send a control frame\n    }\n    if err := rbac.CheckPermission(id, \"realtime.connect\"); err != nil {\n        return nil, err\n    }\n    return id, nil\n}\n",[451,2469,2470,2503,2539,2551,2563,2567,2604,2613,2617,2627],{"__ignoreMap":477},[481,2471,2472,2474,2477,2479,2482,2484,2486,2489,2491,2493,2495,2497,2499,2501],{"class":483,"line":484},[481,2473,745],{"class":558},[481,2475,2476],{"class":637}," acceptConnection",[481,2478,751],{"class":558},[481,2480,2481],{"class":754},"authorizationValue",[481,2483,764],{"class":763},[481,2485,783],{"class":558},[481,2487,2488],{"class":558}," (*",[481,2490,1197],{"class":487},[481,2492,634],{"class":558},[481,2494,2046],{"class":487},[481,2496,681],{"class":558},[481,2498,793],{"class":763},[481,2500,783],{"class":558},[481,2502,798],{"class":558},[481,2504,2505,2508,2510,2512,2514,2516,2518,2520,2522,2524,2526,2528,2530,2532,2534,2537],{"class":483,"line":501},[481,2506,2507],{"class":624},"    id",[481,2509,681],{"class":558},[481,2511,1080],{"class":624},[481,2513,807],{"class":558},[481,2515,631],{"class":624},[481,2517,634],{"class":558},[481,2519,913],{"class":637},[481,2521,751],{"class":558},[481,2523,1093],{"class":624},[481,2525,681],{"class":558},[481,2527,631],{"class":624},[481,2529,634],{"class":558},[481,2531,1102],{"class":624},[481,2533,681],{"class":558},[481,2535,2536],{"class":624}," authorizationValue",[481,2538,610],{"class":558},[481,2540,2541,2543,2545,2547,2549],{"class":483,"line":571},[481,2542,2263],{"class":574},[481,2544,1080],{"class":624},[481,2546,1138],{"class":558},[481,2548,1141],{"class":558},[481,2550,798],{"class":558},[481,2552,2553,2555,2558,2560],{"class":483,"line":581},[481,2554,2135],{"class":574},[481,2556,2557],{"class":558}," nil,",[481,2559,1080],{"class":624},[481,2561,2562],{"class":684},"\u002F\u002F caller decides how to close the connection \u002F send a control frame\n",[481,2564,2565],{"class":483,"line":593},[481,2566,1390],{"class":558},[481,2568,2569,2571,2573,2575,2577,2579,2581,2583,2585,2587,2589,2592,2594,2596,2598,2600,2602],{"class":483,"line":598},[481,2570,2263],{"class":574},[481,2572,1080],{"class":624},[481,2574,807],{"class":558},[481,2576,1609],{"class":624},[481,2578,634],{"class":558},[481,2580,1535],{"class":637},[481,2582,751],{"class":558},[481,2584,1645],{"class":624},[481,2586,681],{"class":558},[481,2588,833],{"class":558},[481,2590,2591],{"class":491},"realtime.connect",[481,2593,678],{"class":558},[481,2595,1657],{"class":558},[481,2597,1080],{"class":624},[481,2599,1138],{"class":558},[481,2601,1141],{"class":558},[481,2603,798],{"class":558},[481,2605,2606,2608,2610],{"class":483,"line":607},[481,2607,2135],{"class":574},[481,2609,2557],{"class":558},[481,2611,2612],{"class":624}," err\n",[481,2614,2615],{"class":483,"line":613},[481,2616,1390],{"class":558},[481,2618,2619,2621,2623,2625],{"class":483,"line":618},[481,2620,883],{"class":574},[481,2622,1218],{"class":624},[481,2624,681],{"class":558},[481,2626,2126],{"class":558},[481,2628,2629],{"class":483,"line":664},[481,2630,904],{"class":558},[447,2632,2633,2634,1532,2636,2639],{},"There is no response to write here, so there is nothing to adapt: the same ",[451,2635,917],{},[451,2637,2638],{},"errors.As"," vocabulary from the HTTP examples above applies unchanged.",[544,2641,2643],{"id":2642},"what-the-kit-adds-on-top","What the kit adds on top",[447,2645,2646,2647,2650,2651,2654,2655,1532,2658,2654,2661,2663,2664,1532,2666,2668,2669,2672,2673,2676,2677,2654,2679,2681,2682,2684,2685,2688],{},"The gpsystem kit's ",[451,2648,2649],{},"server"," package (",[451,2652,2653],{},"server.AuthMiddleware",", ",[451,2656,2657],{},"server.RequireRole",[451,2659,2660],{},"RequirePermission",[451,2662,453],{},") is exactly the ",[451,2665,1789],{},[451,2667,1839],{},"\u002Ferror-rendering code above, written once and shared across every generated project, rendering as RFC 9457 ",[536,2670,2671],{"href":395},"problem+json"," instead of the ad hoc JSON\u002Fplain-text shown here. See ",[536,2674,88],{"href":2675},"\u002Fen\u002Fauth\u002Foverview#with-the-kit-serverauthmiddleware"," for that wiring. Nothing about ",[451,2678,82],{},[451,2680,1197],{}," or ",[451,2683,1977],{}," changes when you add the kit: the same sentinels and the same ",[451,2686,2687],{},"*policy.Denial"," are what the kit's glue matches on.",[2690,2691,2692],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}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 .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":477,"searchDepth":501,"depth":501,"links":2694},[2695,2696,2697,2698,2699,2700],{"id":546,"depth":501,"text":547},{"id":907,"depth":501,"text":908},{"id":1525,"depth":501,"text":1526},{"id":1874,"depth":501,"text":1875},{"id":2445,"depth":501,"text":2446},{"id":2642,"depth":501,"text":2643},"A complete, runnable example with no gp-system dependency beyond auth itself: HTTP and non-HTTP.","md",null,{},{"icon":106},{"title":103,"description":2701},"jP4uQFVFKSMoFF2n6uOxGxw81gVmpY2HzLEM2GDPQQA",[2709,2711],{"title":98,"path":99,"stem":100,"description":2710,"icon":101,"children":-1},"Per-request, per-user authorization above roles: declared in the contract, enforced from generated code.",{"title":108,"path":109,"stem":110,"description":2712,"icon":111,"children":-1},"Wiring markbates\u002Fgoth (Discord, Facebook, Google) into a plain net\u002Fhttp app that only depends on gp-system\u002Fauth for tokens, no gpsystem tooling involved.",1785445894437]