[{"data":1,"prerenderedAt":2932},["ShallowReactive",2],{"navigation_docs":3,"-auth-roles-and-permissions":172,"-auth-roles-and-permissions-surround":2927},[4,26,67,98,124,140,161],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":25},"Getting Started","i-lucide-rocket","\u002Fgetting-started","1.getting-started",[10,15,20],{"title":11,"path":12,"stem":13,"icon":14},"Introduction","\u002Fgetting-started\u002Fintroduction","1.getting-started\u002F1.introduction","i-lucide-house",{"title":16,"path":17,"stem":18,"icon":19},"Installation","\u002Fgetting-started\u002Finstallation","1.getting-started\u002F2.installation","i-lucide-download",{"title":21,"path":22,"stem":23,"icon":24},"Quick Start","\u002Fgetting-started\u002Fquick-start","1.getting-started\u002F3.quick-start","i-lucide-play",false,{"title":27,"icon":28,"path":29,"stem":30,"children":31,"page":25},"Core","i-lucide-box","\u002Fcore","2.core",[32,37,42,47,52,57,62],{"title":33,"path":34,"stem":35,"icon":36},"Routing","\u002Fcore\u002Frouting","2.core\u002F1.routing","i-lucide-route",{"title":38,"path":39,"stem":40,"icon":41},"Handlers","\u002Fcore\u002Fhandlers","2.core\u002F2.handlers","i-lucide-braces",{"title":43,"path":44,"stem":45,"icon":46},"Request Binding","\u002Fcore\u002Frequest-binding","2.core\u002F3.request-binding","i-lucide-tags",{"title":48,"path":49,"stem":50,"icon":51},"Validation","\u002Fcore\u002Fvalidation","2.core\u002F4.validation","i-lucide-check-check",{"title":53,"path":54,"stem":55,"icon":56},"Context","\u002Fcore\u002Fcontext","2.core\u002F5.context","i-lucide-file-input",{"title":58,"path":59,"stem":60,"icon":61},"Errors","\u002Fcore\u002Ferrors","2.core\u002F6.errors","i-lucide-triangle-alert",{"title":63,"path":64,"stem":65,"icon":66},"Middleware","\u002Fcore\u002Fmiddleware","2.core\u002F7.middleware","i-lucide-layers",{"title":68,"icon":69,"path":70,"stem":71,"children":72,"page":25},"Auth","i-lucide-shield-check","\u002Fauth","3.auth",[73,78,83,88,93],{"title":74,"path":75,"stem":76,"icon":77},"Overview","\u002Fauth\u002Foverview","3.auth\u002F1.overview","i-lucide-shield",{"title":79,"path":80,"stem":81,"icon":82},"Auth Rules","\u002Fauth\u002Fauth-rules","3.auth\u002F2.auth-rules","i-lucide-lock",{"title":84,"path":85,"stem":86,"icon":87},"JWT Providers","\u002Fauth\u002Fjwt-providers","3.auth\u002F3.jwt-providers","i-lucide-key",{"title":89,"path":90,"stem":91,"icon":92},"Roles & Permissions","\u002Fauth\u002Froles-and-permissions","3.auth\u002F4.roles-and-permissions","i-lucide-users",{"title":94,"path":95,"stem":96,"icon":97},"Audit Logging","\u002Fauth\u002Faudit-logging","3.auth\u002F5.audit-logging","i-lucide-scroll-text",{"title":99,"icon":100,"path":101,"stem":102,"children":103,"page":25},"GoTrue","i-lucide-key-round","\u002Fgotrue","4.gotrue",[104,109,114,119],{"title":105,"path":106,"stem":107,"icon":108},"Proxy","\u002Fgotrue\u002Fproxy","4.gotrue\u002F1.proxy","i-lucide-shuffle",{"title":110,"path":111,"stem":112,"icon":113},"Endpoints","\u002Fgotrue\u002Fendpoints","4.gotrue\u002F2.endpoints","i-lucide-list",{"title":115,"path":116,"stem":117,"icon":118},"Client IP & Rate Limits","\u002Fgotrue\u002Fclient-ip-and-rate-limits","4.gotrue\u002F3.client-ip-and-rate-limits","i-lucide-network",{"title":120,"path":121,"stem":122,"icon":123},"Deployment","\u002Fgotrue\u002Fdeployment","4.gotrue\u002F4.deployment","i-lucide-server",{"title":125,"icon":126,"path":127,"stem":128,"children":129,"page":25},"API Docs","i-lucide-file-text","\u002Fapi-docs","5.api-docs",[130,135],{"title":131,"path":132,"stem":133,"icon":134},"OpenAPI","\u002Fapi-docs\u002Fopenapi","5.api-docs\u002F1.openapi","i-lucide-file-json",{"title":136,"path":137,"stem":138,"icon":139},"Scalar UI","\u002Fapi-docs\u002Fscalar","5.api-docs\u002F2.scalar","i-lucide-book-open-text",{"title":141,"icon":142,"path":143,"stem":144,"children":145,"page":25},"Guides","i-lucide-compass","\u002Fguides","6.guides",[146,151,156],{"title":147,"path":148,"stem":149,"icon":150},"Testing","\u002Fguides\u002Ftesting","6.guides\u002F1.testing","i-lucide-flask-conical",{"title":152,"path":153,"stem":154,"icon":155},"Performance","\u002Fguides\u002Fperformance","6.guides\u002F2.performance","i-lucide-gauge",{"title":157,"path":158,"stem":159,"icon":160},"Releasing","\u002Fguides\u002Freleasing","6.guides\u002F3.releasing","i-lucide-tag",{"title":162,"icon":163,"path":164,"stem":165,"children":166,"page":25},"Reference","i-lucide-code","\u002Freference","7.reference",[167],{"title":168,"path":169,"stem":170,"icon":171},"API Reference","\u002Freference\u002Fapi-reference","7.reference\u002F1.api-reference","i-lucide-list-tree",{"id":173,"title":89,"body":174,"description":2920,"extension":2921,"links":2922,"meta":2923,"navigation":2924,"path":90,"seo":2925,"stem":91,"__hash__":2926},"docs\u002F3.auth\u002F4.roles-and-permissions.md",{"type":175,"value":176,"toc":2909},"minimark",[177,190,193,334,339,342,965,969,1145,1148,1152,1163,1169,1193,1216,1220,1227,1403,1422,1426,1433,1535,1538,1541,1587,1594,1603,1607,1614,2139,2149,2186,2190,2193,2501,2504,2513,2622,2626,2629,2867,2884,2888,2905],[178,179,180,181,185,186,189],"p",{},"Tokens tell you who the caller is. What they may do usually lives in your own tables, and with GoTrue or Supabase the token's ",[182,183,184],"code",{},"role"," claim is ",[182,187,188],{},"authenticated"," for every user, so it carries no useful authorization signal at all.",[178,191,192],{},"ezz has one extension point for this: an authorization resolver.",[194,195,200],"pre",{"className":196,"code":197,"language":198,"meta":199,"style":199},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","type AuthorizationResolver interface {\n    Resolve(ctx context.Context, user auth.User) (auth.Authorization, error)\n}\n\ntype Authorization struct {\n    Roles       []string\n    Permissions []string\n}\n","go","",[182,201,202,221,280,286,293,306,319,329],{"__ignoreMap":199},[203,204,207,211,215,218],"span",{"class":205,"line":206},"line",1,[203,208,210],{"class":209},"sMK4o","type",[203,212,214],{"class":213},"sBMFI"," AuthorizationResolver",[203,216,217],{"class":209}," interface",[203,219,220],{"class":209}," {\n",[203,222,224,228,231,235,238,241,243,246,249,252,254,257,260,263,266,268,271,273,277],{"class":205,"line":223},2,[203,225,227],{"class":226},"s2Zo4","    Resolve",[203,229,230],{"class":209},"(",[203,232,234],{"class":233},"sHdIc","ctx",[203,236,237],{"class":213}," context",[203,239,240],{"class":209},".",[203,242,53],{"class":213},[203,244,245],{"class":209},",",[203,247,248],{"class":233}," user",[203,250,251],{"class":213}," auth",[203,253,240],{"class":209},[203,255,256],{"class":213},"User",[203,258,259],{"class":209},")",[203,261,262],{"class":209}," (",[203,264,265],{"class":213},"auth",[203,267,240],{"class":209},[203,269,270],{"class":213},"Authorization",[203,272,245],{"class":209},[203,274,276],{"class":275},"spNyl"," error",[203,278,279],{"class":209},")\n",[203,281,283],{"class":205,"line":282},3,[203,284,285],{"class":209},"}\n",[203,287,289],{"class":205,"line":288},4,[203,290,292],{"emptyLinePlaceholder":291},true,"\n",[203,294,296,298,301,304],{"class":205,"line":295},5,[203,297,210],{"class":209},[203,299,300],{"class":213}," Authorization",[203,302,303],{"class":209}," struct",[203,305,220],{"class":209},[203,307,309,313,316],{"class":205,"line":308},6,[203,310,312],{"class":311},"sTEyZ","    Roles       ",[203,314,315],{"class":209},"[]",[203,317,318],{"class":275},"string\n",[203,320,322,325,327],{"class":205,"line":321},7,[203,323,324],{"class":311},"    Permissions ",[203,326,315],{"class":209},[203,328,318],{"class":275},[203,330,332],{"class":205,"line":331},8,[203,333,285],{"class":209},[335,336,338],"h2",{"id":337},"write-a-resolver","Write a resolver",[178,340,341],{},"Query whatever you keep roles and permissions in, and return both lists.",[194,343,345],{"className":196,"code":344,"language":198,"meta":199,"style":199},"type resolver struct{ db *sql.DB }\n\nfunc (r resolver) Resolve(ctx context.Context, user auth.User) (auth.Authorization, error) {\n    rows, err := r.db.QueryContext(ctx, `\n        SELECT r.name, p.name\n        FROM user_roles ur\n        JOIN roles r ON r.id = ur.role_id\n        LEFT JOIN role_permissions rp ON rp.role_id = r.id\n        LEFT JOIN permissions p ON p.id = rp.permission_id\n        WHERE ur.user_id = $1`, user.ID())\n    if err != nil {\n        return auth.Authorization{}, err     \u002F\u002F fail closed, becomes a 500\n    }\n    defer rows.Close()\n\n    var authz auth.Authorization\n    seen := map[string]bool{}\n    for rows.Next() {\n        var role string\n        var perm sql.NullString\n        if err := rows.Scan(&role, &perm); err != nil {\n            return auth.Authorization{}, err\n        }\n        if !seen[\"r:\"+role] {\n            seen[\"r:\"+role] = true\n            authz.Roles = append(authz.Roles, role)\n        }\n        if perm.Valid && !seen[\"p:\"+perm.String] {\n            seen[\"p:\"+perm.String] = true\n            authz.Permissions = append(authz.Permissions, perm.String)\n        }\n    }\n    return authz, rows.Err()\n}\n",[182,346,347,374,378,434,469,475,480,485,490,496,517,534,556,562,579,584,600,623,641,652,667,708,725,731,762,789,823,828,870,897,930,935,940,960],{"__ignoreMap":199},[203,348,349,351,354,357,360,363,366,368,371],{"class":205,"line":206},[203,350,210],{"class":209},[203,352,353],{"class":213}," resolver",[203,355,356],{"class":209}," struct{",[203,358,359],{"class":311}," db ",[203,361,362],{"class":209},"*",[203,364,365],{"class":213},"sql",[203,367,240],{"class":209},[203,369,370],{"class":213},"DB",[203,372,373],{"class":209}," }\n",[203,375,376],{"class":205,"line":223},[203,377,292],{"emptyLinePlaceholder":291},[203,379,380,383,385,388,391,393,396,398,400,402,404,406,408,410,412,414,416,418,420,422,424,426,428,430,432],{"class":205,"line":282},[203,381,382],{"class":209},"func",[203,384,262],{"class":209},[203,386,387],{"class":233},"r ",[203,389,390],{"class":213},"resolver",[203,392,259],{"class":209},[203,394,395],{"class":226}," Resolve",[203,397,230],{"class":209},[203,399,234],{"class":233},[203,401,237],{"class":213},[203,403,240],{"class":209},[203,405,53],{"class":213},[203,407,245],{"class":209},[203,409,248],{"class":233},[203,411,251],{"class":213},[203,413,240],{"class":209},[203,415,256],{"class":213},[203,417,259],{"class":209},[203,419,262],{"class":209},[203,421,265],{"class":213},[203,423,240],{"class":209},[203,425,270],{"class":213},[203,427,245],{"class":209},[203,429,276],{"class":275},[203,431,259],{"class":209},[203,433,220],{"class":209},[203,435,436,439,441,444,447,450,452,455,457,460,462,464,466],{"class":205,"line":288},[203,437,438],{"class":311},"    rows",[203,440,245],{"class":209},[203,442,443],{"class":311}," err ",[203,445,446],{"class":209},":=",[203,448,449],{"class":311}," r",[203,451,240],{"class":209},[203,453,454],{"class":311},"db",[203,456,240],{"class":209},[203,458,459],{"class":226},"QueryContext",[203,461,230],{"class":209},[203,463,234],{"class":311},[203,465,245],{"class":209},[203,467,468],{"class":209}," `\n",[203,470,471],{"class":205,"line":295},[203,472,474],{"class":473},"sfazB","        SELECT r.name, p.name\n",[203,476,477],{"class":205,"line":308},[203,478,479],{"class":473},"        FROM user_roles ur\n",[203,481,482],{"class":205,"line":321},[203,483,484],{"class":473},"        JOIN roles r ON r.id = ur.role_id\n",[203,486,487],{"class":205,"line":331},[203,488,489],{"class":473},"        LEFT JOIN role_permissions rp ON rp.role_id = r.id\n",[203,491,493],{"class":205,"line":492},9,[203,494,495],{"class":473},"        LEFT JOIN permissions p ON p.id = rp.permission_id\n",[203,497,499,502,505,507,509,511,514],{"class":205,"line":498},10,[203,500,501],{"class":473},"        WHERE ur.user_id = $1",[203,503,504],{"class":209},"`",[203,506,245],{"class":209},[203,508,248],{"class":311},[203,510,240],{"class":209},[203,512,513],{"class":226},"ID",[203,515,516],{"class":209},"())\n",[203,518,520,524,526,529,532],{"class":205,"line":519},11,[203,521,523],{"class":522},"s7zQu","    if",[203,525,443],{"class":311},[203,527,528],{"class":209},"!=",[203,530,531],{"class":209}," nil",[203,533,220],{"class":209},[203,535,537,540,542,544,546,549,552],{"class":205,"line":536},12,[203,538,539],{"class":522},"        return",[203,541,251],{"class":213},[203,543,240],{"class":209},[203,545,270],{"class":213},[203,547,548],{"class":209},"{},",[203,550,551],{"class":311}," err     ",[203,553,555],{"class":554},"sHwdD","\u002F\u002F fail closed, becomes a 500\n",[203,557,559],{"class":205,"line":558},13,[203,560,561],{"class":209},"    }\n",[203,563,565,568,571,573,576],{"class":205,"line":564},14,[203,566,567],{"class":522},"    defer",[203,569,570],{"class":311}," rows",[203,572,240],{"class":209},[203,574,575],{"class":226},"Close",[203,577,578],{"class":209},"()\n",[203,580,582],{"class":205,"line":581},15,[203,583,292],{"emptyLinePlaceholder":291},[203,585,587,590,593,595,597],{"class":205,"line":586},16,[203,588,589],{"class":209},"    var",[203,591,592],{"class":311}," authz ",[203,594,265],{"class":213},[203,596,240],{"class":209},[203,598,599],{"class":213},"Authorization\n",[203,601,603,606,608,611,614,617,620],{"class":205,"line":602},17,[203,604,605],{"class":311},"    seen ",[203,607,446],{"class":209},[203,609,610],{"class":209}," map[",[203,612,613],{"class":275},"string",[203,615,616],{"class":209},"]",[203,618,619],{"class":275},"bool",[203,621,622],{"class":209},"{}\n",[203,624,626,629,631,633,636,639],{"class":205,"line":625},18,[203,627,628],{"class":522},"    for",[203,630,570],{"class":311},[203,632,240],{"class":209},[203,634,635],{"class":226},"Next",[203,637,638],{"class":209},"()",[203,640,220],{"class":209},[203,642,644,647,650],{"class":205,"line":643},19,[203,645,646],{"class":209},"        var",[203,648,649],{"class":311}," role ",[203,651,318],{"class":275},[203,653,655,657,660,662,664],{"class":205,"line":654},20,[203,656,646],{"class":209},[203,658,659],{"class":311}," perm ",[203,661,365],{"class":213},[203,663,240],{"class":209},[203,665,666],{"class":213},"NullString\n",[203,668,670,673,675,677,679,681,684,687,689,691,694,697,700,702,704,706],{"class":205,"line":669},21,[203,671,672],{"class":522},"        if",[203,674,443],{"class":311},[203,676,446],{"class":209},[203,678,570],{"class":311},[203,680,240],{"class":209},[203,682,683],{"class":226},"Scan",[203,685,686],{"class":209},"(&",[203,688,184],{"class":311},[203,690,245],{"class":209},[203,692,693],{"class":209}," &",[203,695,696],{"class":311},"perm",[203,698,699],{"class":209},");",[203,701,443],{"class":311},[203,703,528],{"class":209},[203,705,531],{"class":209},[203,707,220],{"class":209},[203,709,711,714,716,718,720,722],{"class":205,"line":710},22,[203,712,713],{"class":522},"            return",[203,715,251],{"class":213},[203,717,240],{"class":209},[203,719,270],{"class":213},[203,721,548],{"class":209},[203,723,724],{"class":311}," err\n",[203,726,728],{"class":205,"line":727},23,[203,729,730],{"class":209},"        }\n",[203,732,734,736,739,742,745,748,751,753,756,758,760],{"class":205,"line":733},24,[203,735,672],{"class":522},[203,737,738],{"class":209}," !",[203,740,741],{"class":311},"seen",[203,743,744],{"class":209},"[",[203,746,747],{"class":209},"\"",[203,749,750],{"class":473},"r:",[203,752,747],{"class":209},[203,754,755],{"class":209},"+",[203,757,184],{"class":311},[203,759,616],{"class":209},[203,761,220],{"class":209},[203,763,765,768,770,772,774,776,778,780,782,785],{"class":205,"line":764},25,[203,766,767],{"class":311},"            seen",[203,769,744],{"class":209},[203,771,747],{"class":209},[203,773,750],{"class":473},[203,775,747],{"class":209},[203,777,755],{"class":209},[203,779,184],{"class":311},[203,781,616],{"class":209},[203,783,784],{"class":209}," =",[203,786,788],{"class":787},"sfNiH"," true\n",[203,790,792,795,797,800,803,806,808,811,813,816,818,821],{"class":205,"line":791},26,[203,793,794],{"class":311},"            authz",[203,796,240],{"class":209},[203,798,799],{"class":311},"Roles ",[203,801,802],{"class":209},"=",[203,804,805],{"class":226}," append",[203,807,230],{"class":209},[203,809,810],{"class":311},"authz",[203,812,240],{"class":209},[203,814,815],{"class":311},"Roles",[203,817,245],{"class":209},[203,819,820],{"class":311}," role",[203,822,279],{"class":209},[203,824,826],{"class":205,"line":825},27,[203,827,730],{"class":209},[203,829,831,833,836,838,841,844,846,848,850,852,855,857,859,861,863,866,868],{"class":205,"line":830},28,[203,832,672],{"class":522},[203,834,835],{"class":311}," perm",[203,837,240],{"class":209},[203,839,840],{"class":311},"Valid ",[203,842,843],{"class":209},"&&",[203,845,738],{"class":209},[203,847,741],{"class":311},[203,849,744],{"class":209},[203,851,747],{"class":209},[203,853,854],{"class":473},"p:",[203,856,747],{"class":209},[203,858,755],{"class":209},[203,860,696],{"class":311},[203,862,240],{"class":209},[203,864,865],{"class":311},"String",[203,867,616],{"class":209},[203,869,220],{"class":209},[203,871,873,875,877,879,881,883,885,887,889,891,893,895],{"class":205,"line":872},29,[203,874,767],{"class":311},[203,876,744],{"class":209},[203,878,747],{"class":209},[203,880,854],{"class":473},[203,882,747],{"class":209},[203,884,755],{"class":209},[203,886,696],{"class":311},[203,888,240],{"class":209},[203,890,865],{"class":311},[203,892,616],{"class":209},[203,894,784],{"class":209},[203,896,788],{"class":787},[203,898,900,902,904,907,909,911,913,915,917,920,922,924,926,928],{"class":205,"line":899},30,[203,901,794],{"class":311},[203,903,240],{"class":209},[203,905,906],{"class":311},"Permissions ",[203,908,802],{"class":209},[203,910,805],{"class":226},[203,912,230],{"class":209},[203,914,810],{"class":311},[203,916,240],{"class":209},[203,918,919],{"class":311},"Permissions",[203,921,245],{"class":209},[203,923,835],{"class":311},[203,925,240],{"class":209},[203,927,865],{"class":311},[203,929,279],{"class":209},[203,931,933],{"class":205,"line":932},31,[203,934,730],{"class":209},[203,936,938],{"class":205,"line":937},32,[203,939,561],{"class":209},[203,941,943,946,949,951,953,955,958],{"class":205,"line":942},33,[203,944,945],{"class":522},"    return",[203,947,948],{"class":311}," authz",[203,950,245],{"class":209},[203,952,570],{"class":311},[203,954,240],{"class":209},[203,956,957],{"class":226},"Err",[203,959,578],{"class":209},[203,961,963],{"class":205,"line":962},34,[203,964,285],{"class":209},[335,966,968],{"id":967},"attach-it-to-the-provider","Attach it to the provider",[194,970,972],{"className":196,"code":971,"language":198,"meta":199,"style":199},"provider := auth.NewJWTAuthProvider(jwt).WithResolver(resolver{db: db})\napp := ezz.New().Auth(provider)\n\napp.GET(\"\u002Fadmin\", panel).Auth(ezz.Role(\"admin\"))\napp.POST(\"\u002Fposts\", create).Auth(ezz.Permission(\"posts:write\"))\n",[182,973,974,1017,1044,1048,1098],{"__ignoreMap":199},[203,975,976,979,981,983,985,988,990,993,996,999,1001,1003,1006,1008,1011,1014],{"class":205,"line":206},[203,977,978],{"class":311},"provider ",[203,980,446],{"class":209},[203,982,251],{"class":311},[203,984,240],{"class":209},[203,986,987],{"class":226},"NewJWTAuthProvider",[203,989,230],{"class":209},[203,991,992],{"class":311},"jwt",[203,994,995],{"class":209},").",[203,997,998],{"class":226},"WithResolver",[203,1000,230],{"class":209},[203,1002,390],{"class":213},[203,1004,1005],{"class":209},"{",[203,1007,454],{"class":311},[203,1009,1010],{"class":209},":",[203,1012,1013],{"class":311}," db",[203,1015,1016],{"class":209},"})\n",[203,1018,1019,1022,1024,1027,1029,1032,1035,1037,1039,1042],{"class":205,"line":223},[203,1020,1021],{"class":311},"app ",[203,1023,446],{"class":209},[203,1025,1026],{"class":311}," ezz",[203,1028,240],{"class":209},[203,1030,1031],{"class":226},"New",[203,1033,1034],{"class":209},"().",[203,1036,68],{"class":226},[203,1038,230],{"class":209},[203,1040,1041],{"class":311},"provider",[203,1043,279],{"class":209},[203,1045,1046],{"class":205,"line":282},[203,1047,292],{"emptyLinePlaceholder":291},[203,1049,1050,1053,1055,1058,1060,1062,1065,1067,1069,1072,1074,1076,1078,1081,1083,1086,1088,1090,1093,1095],{"class":205,"line":288},[203,1051,1052],{"class":311},"app",[203,1054,240],{"class":209},[203,1056,1057],{"class":226},"GET",[203,1059,230],{"class":209},[203,1061,747],{"class":209},[203,1063,1064],{"class":473},"\u002Fadmin",[203,1066,747],{"class":209},[203,1068,245],{"class":209},[203,1070,1071],{"class":311}," panel",[203,1073,995],{"class":209},[203,1075,68],{"class":226},[203,1077,230],{"class":209},[203,1079,1080],{"class":311},"ezz",[203,1082,240],{"class":209},[203,1084,1085],{"class":226},"Role",[203,1087,230],{"class":209},[203,1089,747],{"class":209},[203,1091,1092],{"class":473},"admin",[203,1094,747],{"class":209},[203,1096,1097],{"class":209},"))\n",[203,1099,1100,1102,1104,1107,1109,1111,1114,1116,1118,1121,1123,1125,1127,1129,1131,1134,1136,1138,1141,1143],{"class":205,"line":295},[203,1101,1052],{"class":311},[203,1103,240],{"class":209},[203,1105,1106],{"class":226},"POST",[203,1108,230],{"class":209},[203,1110,747],{"class":209},[203,1112,1113],{"class":473},"\u002Fposts",[203,1115,747],{"class":209},[203,1117,245],{"class":209},[203,1119,1120],{"class":311}," create",[203,1122,995],{"class":209},[203,1124,68],{"class":226},[203,1126,230],{"class":209},[203,1128,1080],{"class":311},[203,1130,240],{"class":209},[203,1132,1133],{"class":226},"Permission",[203,1135,230],{"class":209},[203,1137,747],{"class":209},[203,1139,1140],{"class":473},"posts:write",[203,1142,747],{"class":209},[203,1144,1097],{"class":209},[178,1146,1147],{},"Nothing else changes. The route rules you already wrote now consult your data.",[335,1149,1151],{"id":1150},"how-resolution-behaves","How resolution behaves",[178,1153,1154,1158,1159,1162],{},[1155,1156,1157],"strong",{},"Lazy."," The resolver runs the first time something actually asks about a role or a permission. Public routes never call it, and neither do routes using only ",[182,1160,1161],{},"ezz.Authenticated()",". A read-only endpoint that checks nothing costs no query.",[178,1164,1165,1168],{},[1155,1166,1167],{},"Memoized per request."," Within one request the result is computed once, no matter how many checks happen, in rules or in your handler.",[178,1170,1171,1174,1175,1177,1178,1181,1182,1184,1185,1188,1189,1192],{},[1155,1172,1173],{},"Authoritative."," When a resolver is configured, roles and permissions come only from it. The token's ",[182,1176,184],{}," claim is ignored. Without a resolver, ",[182,1179,1180],{},"HasRole"," falls back to the ",[182,1183,184],{}," and ",[182,1186,1187],{},"roles"," claims and ",[182,1190,1191],{},"HasPermission"," always returns false.",[178,1194,1195,1198,1199,1202,1203,1206,1207,1210,1211,1213,1214,240],{},[1155,1196,1197],{},"Fail-closed."," If the resolver returns an error, the request ends with ",[182,1200,1201],{},"500"," and the message ",[182,1204,1205],{},"Authorization failed",", not ",[182,1208,1209],{},"403",". A ",[182,1212,1209],{}," would tell the caller they lack access, which is not what a failed lookup means. An empty result, on the other hand, is a genuine denial and produces ",[182,1215,1209],{},[335,1217,1219],{"id":1218},"fold-in-roles-from-the-token","Fold in roles from the token",[178,1221,1222,1223,1226],{},"If you mint application roles into the token with a custom access-token hook, ",[182,1224,1225],{},"auth.ClaimRoles"," pulls them out so you can combine both sources:",[194,1228,1230],{"className":196,"code":1229,"language":198,"meta":199,"style":199},"func (r resolver) Resolve(ctx context.Context, user auth.User) (auth.Authorization, error) {\n    authz, err := r.lookup(ctx, user.ID())\n    if err != nil {\n        return auth.Authorization{}, err\n    }\n    authz.Roles = append(authz.Roles, auth.ClaimRoles(user.GetClaims())...)\n    return authz, nil\n}\n",[182,1231,1232,1284,1316,1328,1342,1346,1388,1399],{"__ignoreMap":199},[203,1233,1234,1236,1238,1240,1242,1244,1246,1248,1250,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282],{"class":205,"line":206},[203,1235,382],{"class":209},[203,1237,262],{"class":209},[203,1239,387],{"class":233},[203,1241,390],{"class":213},[203,1243,259],{"class":209},[203,1245,395],{"class":226},[203,1247,230],{"class":209},[203,1249,234],{"class":233},[203,1251,237],{"class":213},[203,1253,240],{"class":209},[203,1255,53],{"class":213},[203,1257,245],{"class":209},[203,1259,248],{"class":233},[203,1261,251],{"class":213},[203,1263,240],{"class":209},[203,1265,256],{"class":213},[203,1267,259],{"class":209},[203,1269,262],{"class":209},[203,1271,265],{"class":213},[203,1273,240],{"class":209},[203,1275,270],{"class":213},[203,1277,245],{"class":209},[203,1279,276],{"class":275},[203,1281,259],{"class":209},[203,1283,220],{"class":209},[203,1285,1286,1289,1291,1293,1295,1297,1299,1302,1304,1306,1308,1310,1312,1314],{"class":205,"line":223},[203,1287,1288],{"class":311},"    authz",[203,1290,245],{"class":209},[203,1292,443],{"class":311},[203,1294,446],{"class":209},[203,1296,449],{"class":311},[203,1298,240],{"class":209},[203,1300,1301],{"class":226},"lookup",[203,1303,230],{"class":209},[203,1305,234],{"class":311},[203,1307,245],{"class":209},[203,1309,248],{"class":311},[203,1311,240],{"class":209},[203,1313,513],{"class":226},[203,1315,516],{"class":209},[203,1317,1318,1320,1322,1324,1326],{"class":205,"line":282},[203,1319,523],{"class":522},[203,1321,443],{"class":311},[203,1323,528],{"class":209},[203,1325,531],{"class":209},[203,1327,220],{"class":209},[203,1329,1330,1332,1334,1336,1338,1340],{"class":205,"line":288},[203,1331,539],{"class":522},[203,1333,251],{"class":213},[203,1335,240],{"class":209},[203,1337,270],{"class":213},[203,1339,548],{"class":209},[203,1341,724],{"class":311},[203,1343,1344],{"class":205,"line":295},[203,1345,561],{"class":209},[203,1347,1348,1350,1352,1354,1356,1358,1360,1362,1364,1366,1368,1370,1372,1375,1377,1380,1382,1385],{"class":205,"line":308},[203,1349,1288],{"class":311},[203,1351,240],{"class":209},[203,1353,799],{"class":311},[203,1355,802],{"class":209},[203,1357,805],{"class":226},[203,1359,230],{"class":209},[203,1361,810],{"class":311},[203,1363,240],{"class":209},[203,1365,815],{"class":311},[203,1367,245],{"class":209},[203,1369,251],{"class":311},[203,1371,240],{"class":209},[203,1373,1374],{"class":226},"ClaimRoles",[203,1376,230],{"class":209},[203,1378,1379],{"class":311},"user",[203,1381,240],{"class":209},[203,1383,1384],{"class":226},"GetClaims",[203,1386,1387],{"class":209},"())...)\n",[203,1389,1390,1392,1394,1396],{"class":205,"line":321},[203,1391,945],{"class":522},[203,1393,948],{"class":311},[203,1395,245],{"class":209},[203,1397,1398],{"class":209}," nil\n",[203,1400,1401],{"class":205,"line":331},[203,1402,285],{"class":209},[178,1404,1405,1407,1408,1410,1411,1413,1414,1417,1418,1421],{},[182,1406,1374],{}," reads the ",[182,1409,184],{}," claim as a string and the ",[182,1412,1187],{}," claim as either ",[182,1415,1416],{},"[]string"," or ",[182,1419,1420],{},"[]any",", skipping empties.",[335,1423,1425],{"id":1424},"cache-the-lookup","Cache the lookup",[178,1427,1428,1429,1432],{},"A resolver that hits the database on every request that checks a role is a lot of queries. ",[182,1430,1431],{},"auth.Cached"," wraps any resolver with a TTL cache keyed by user ID.",[194,1434,1436],{"className":196,"code":1435,"language":198,"meta":199,"style":199},"authz := auth.Cached(resolver{db: db}, 5*time.Minute)\n\nprovider := auth.NewJWTAuthProvider(jwt).WithResolver(authz)\napp := ezz.New().Auth(provider)\n",[182,1437,1438,1483,1487,1513],{"__ignoreMap":199},[203,1439,1440,1443,1445,1447,1449,1452,1454,1456,1458,1460,1462,1464,1467,1471,1473,1476,1478,1481],{"class":205,"line":206},[203,1441,1442],{"class":311},"authz ",[203,1444,446],{"class":209},[203,1446,251],{"class":311},[203,1448,240],{"class":209},[203,1450,1451],{"class":226},"Cached",[203,1453,230],{"class":209},[203,1455,390],{"class":213},[203,1457,1005],{"class":209},[203,1459,454],{"class":311},[203,1461,1010],{"class":209},[203,1463,1013],{"class":311},[203,1465,1466],{"class":209},"},",[203,1468,1470],{"class":1469},"sbssI"," 5",[203,1472,362],{"class":209},[203,1474,1475],{"class":311},"time",[203,1477,240],{"class":209},[203,1479,1480],{"class":311},"Minute",[203,1482,279],{"class":209},[203,1484,1485],{"class":205,"line":223},[203,1486,292],{"emptyLinePlaceholder":291},[203,1488,1489,1491,1493,1495,1497,1499,1501,1503,1505,1507,1509,1511],{"class":205,"line":282},[203,1490,978],{"class":311},[203,1492,446],{"class":209},[203,1494,251],{"class":311},[203,1496,240],{"class":209},[203,1498,987],{"class":226},[203,1500,230],{"class":209},[203,1502,992],{"class":311},[203,1504,995],{"class":209},[203,1506,998],{"class":226},[203,1508,230],{"class":209},[203,1510,810],{"class":311},[203,1512,279],{"class":209},[203,1514,1515,1517,1519,1521,1523,1525,1527,1529,1531,1533],{"class":205,"line":288},[203,1516,1021],{"class":311},[203,1518,446],{"class":209},[203,1520,1026],{"class":311},[203,1522,240],{"class":209},[203,1524,1031],{"class":226},[203,1526,1034],{"class":209},[203,1528,68],{"class":226},[203,1530,230],{"class":209},[203,1532,1041],{"class":311},[203,1534,279],{"class":209},[178,1536,1537],{},"Successful results are cached for the TTL; errors are never cached, so a database blip does not stick. The cache holds up to 10,000 users and evicts the least recently used.",[178,1539,1540],{},"Hold on to the handle so you can invalidate when access changes:",[194,1542,1544],{"className":196,"code":1543,"language":198,"meta":199,"style":199},"\u002F\u002F after granting or revoking a role\nauthz.Invalidate(userID)\n\n\u002F\u002F after a bulk permissions change\nauthz.InvalidateAll()\n",[182,1545,1546,1551,1567,1571,1576],{"__ignoreMap":199},[203,1547,1548],{"class":205,"line":206},[203,1549,1550],{"class":554},"\u002F\u002F after granting or revoking a role\n",[203,1552,1553,1555,1557,1560,1562,1565],{"class":205,"line":223},[203,1554,810],{"class":311},[203,1556,240],{"class":209},[203,1558,1559],{"class":226},"Invalidate",[203,1561,230],{"class":209},[203,1563,1564],{"class":311},"userID",[203,1566,279],{"class":209},[203,1568,1569],{"class":205,"line":282},[203,1570,292],{"emptyLinePlaceholder":291},[203,1572,1573],{"class":205,"line":288},[203,1574,1575],{"class":554},"\u002F\u002F after a bulk permissions change\n",[203,1577,1578,1580,1582,1585],{"class":205,"line":295},[203,1579,810],{"class":311},[203,1581,240],{"class":209},[203,1583,1584],{"class":226},"InvalidateAll",[203,1586,578],{"class":209},[1588,1589,1590,1591,1593],"warning",{},"A cached grant means a revoked role can stay live for up to the TTL. Pick the TTL against how quickly a revocation must take effect, and call ",[182,1592,1559],{}," in the code path that changes access.",[178,1595,1596,1597,1599,1600,1602],{},"If several replicas serve your API, each has its own cache, so ",[182,1598,1559],{}," only clears the process it runs in. Either keep the TTL short or publish invalidations over your message bus and call ",[182,1601,1559],{}," on every instance.",[335,1604,1606],{"id":1605},"expand-role-hierarchies","Expand role hierarchies",[178,1608,1609,1610,1613],{},"Hierarchies belong inside the resolver, where you have the full role set. ",[182,1611,1612],{},"*auth.Hierarchy"," maps a role to the roles it implies, transitively.",[194,1615,1617],{"className":196,"code":1616,"language":198,"meta":199,"style":199},"hierarchy := auth.BuildHierarchy().\n    Role(\"super_admin\").ImpliesAll(\"admin\", \"moderator\").\n    Role(\"admin\").Implies(\"user\").\n    Role(\"moderator\").Implies(\"user\").\n    Build()\n\nfunc expand(h *auth.Hierarchy, roles []string) []string {\n    seen := map[string]bool{}\n    var out []string\n    add := func(r string) {\n        if !seen[r] {\n            seen[r] = true\n            out = append(out, r)\n        }\n    }\n    for _, r := range roles {\n        add(r)\n        for _, implied := range h.GetImpliedRoles(r) {\n            add(implied)\n        }\n    }\n    return out\n}\n\nfunc (r resolver) Resolve(ctx context.Context, user auth.User) (auth.Authorization, error) {\n    authz, err := r.lookup(ctx, user.ID())\n    if err != nil {\n        return auth.Authorization{}, err\n    }\n    authz.Roles = expand(hierarchy, authz.Roles)\n    return authz, nil\n}\n",[182,1618,1619,1636,1676,1703,1729,1736,1740,1780,1796,1807,1827,1843,1857,1877,1881,1885,1908,1919,1951,1963,1967,1971,1978,1982,1986,2038,2068,2080,2094,2098,2125,2135],{"__ignoreMap":199},[203,1620,1621,1624,1626,1628,1630,1633],{"class":205,"line":206},[203,1622,1623],{"class":311},"hierarchy ",[203,1625,446],{"class":209},[203,1627,251],{"class":311},[203,1629,240],{"class":209},[203,1631,1632],{"class":226},"BuildHierarchy",[203,1634,1635],{"class":209},"().\n",[203,1637,1638,1641,1643,1645,1648,1650,1652,1655,1657,1659,1661,1663,1665,1668,1671,1673],{"class":205,"line":223},[203,1639,1640],{"class":226},"    Role",[203,1642,230],{"class":209},[203,1644,747],{"class":209},[203,1646,1647],{"class":473},"super_admin",[203,1649,747],{"class":209},[203,1651,995],{"class":209},[203,1653,1654],{"class":226},"ImpliesAll",[203,1656,230],{"class":209},[203,1658,747],{"class":209},[203,1660,1092],{"class":473},[203,1662,747],{"class":209},[203,1664,245],{"class":209},[203,1666,1667],{"class":209}," \"",[203,1669,1670],{"class":473},"moderator",[203,1672,747],{"class":209},[203,1674,1675],{"class":209},").\n",[203,1677,1678,1680,1682,1684,1686,1688,1690,1693,1695,1697,1699,1701],{"class":205,"line":282},[203,1679,1640],{"class":226},[203,1681,230],{"class":209},[203,1683,747],{"class":209},[203,1685,1092],{"class":473},[203,1687,747],{"class":209},[203,1689,995],{"class":209},[203,1691,1692],{"class":226},"Implies",[203,1694,230],{"class":209},[203,1696,747],{"class":209},[203,1698,1379],{"class":473},[203,1700,747],{"class":209},[203,1702,1675],{"class":209},[203,1704,1705,1707,1709,1711,1713,1715,1717,1719,1721,1723,1725,1727],{"class":205,"line":288},[203,1706,1640],{"class":226},[203,1708,230],{"class":209},[203,1710,747],{"class":209},[203,1712,1670],{"class":473},[203,1714,747],{"class":209},[203,1716,995],{"class":209},[203,1718,1692],{"class":226},[203,1720,230],{"class":209},[203,1722,747],{"class":209},[203,1724,1379],{"class":473},[203,1726,747],{"class":209},[203,1728,1675],{"class":209},[203,1730,1731,1734],{"class":205,"line":295},[203,1732,1733],{"class":226},"    Build",[203,1735,578],{"class":209},[203,1737,1738],{"class":205,"line":308},[203,1739,292],{"emptyLinePlaceholder":291},[203,1741,1742,1744,1747,1749,1752,1755,1757,1759,1762,1764,1767,1770,1772,1774,1776,1778],{"class":205,"line":321},[203,1743,382],{"class":209},[203,1745,1746],{"class":226}," expand",[203,1748,230],{"class":209},[203,1750,1751],{"class":233},"h",[203,1753,1754],{"class":209}," *",[203,1756,265],{"class":213},[203,1758,240],{"class":209},[203,1760,1761],{"class":213},"Hierarchy",[203,1763,245],{"class":209},[203,1765,1766],{"class":233}," roles",[203,1768,1769],{"class":209}," []",[203,1771,613],{"class":275},[203,1773,259],{"class":209},[203,1775,1769],{"class":209},[203,1777,613],{"class":275},[203,1779,220],{"class":209},[203,1781,1782,1784,1786,1788,1790,1792,1794],{"class":205,"line":331},[203,1783,605],{"class":311},[203,1785,446],{"class":209},[203,1787,610],{"class":209},[203,1789,613],{"class":275},[203,1791,616],{"class":209},[203,1793,619],{"class":275},[203,1795,622],{"class":209},[203,1797,1798,1800,1803,1805],{"class":205,"line":492},[203,1799,589],{"class":209},[203,1801,1802],{"class":311}," out ",[203,1804,315],{"class":209},[203,1806,318],{"class":275},[203,1808,1809,1812,1814,1817,1820,1823,1825],{"class":205,"line":498},[203,1810,1811],{"class":311},"    add ",[203,1813,446],{"class":209},[203,1815,1816],{"class":209}," func(",[203,1818,1819],{"class":233},"r",[203,1821,1822],{"class":275}," string",[203,1824,259],{"class":209},[203,1826,220],{"class":209},[203,1828,1829,1831,1833,1835,1837,1839,1841],{"class":205,"line":519},[203,1830,672],{"class":522},[203,1832,738],{"class":209},[203,1834,741],{"class":311},[203,1836,744],{"class":209},[203,1838,1819],{"class":311},[203,1840,616],{"class":209},[203,1842,220],{"class":209},[203,1844,1845,1847,1849,1851,1853,1855],{"class":205,"line":536},[203,1846,767],{"class":311},[203,1848,744],{"class":209},[203,1850,1819],{"class":311},[203,1852,616],{"class":209},[203,1854,784],{"class":209},[203,1856,788],{"class":787},[203,1858,1859,1862,1864,1866,1868,1871,1873,1875],{"class":205,"line":558},[203,1860,1861],{"class":311},"            out ",[203,1863,802],{"class":209},[203,1865,805],{"class":226},[203,1867,230],{"class":209},[203,1869,1870],{"class":311},"out",[203,1872,245],{"class":209},[203,1874,449],{"class":311},[203,1876,279],{"class":209},[203,1878,1879],{"class":205,"line":564},[203,1880,730],{"class":209},[203,1882,1883],{"class":205,"line":581},[203,1884,561],{"class":209},[203,1886,1887,1889,1892,1894,1897,1899,1902,1905],{"class":205,"line":586},[203,1888,628],{"class":522},[203,1890,1891],{"class":311}," _",[203,1893,245],{"class":209},[203,1895,1896],{"class":311}," r ",[203,1898,446],{"class":209},[203,1900,1901],{"class":522}," range",[203,1903,1904],{"class":311}," roles ",[203,1906,1907],{"class":209},"{\n",[203,1909,1910,1913,1915,1917],{"class":205,"line":602},[203,1911,1912],{"class":226},"        add",[203,1914,230],{"class":209},[203,1916,1819],{"class":311},[203,1918,279],{"class":209},[203,1920,1921,1924,1926,1928,1931,1933,1935,1938,1940,1943,1945,1947,1949],{"class":205,"line":625},[203,1922,1923],{"class":522},"        for",[203,1925,1891],{"class":311},[203,1927,245],{"class":209},[203,1929,1930],{"class":311}," implied ",[203,1932,446],{"class":209},[203,1934,1901],{"class":522},[203,1936,1937],{"class":311}," h",[203,1939,240],{"class":209},[203,1941,1942],{"class":226},"GetImpliedRoles",[203,1944,230],{"class":209},[203,1946,1819],{"class":311},[203,1948,259],{"class":209},[203,1950,220],{"class":209},[203,1952,1953,1956,1958,1961],{"class":205,"line":643},[203,1954,1955],{"class":226},"            add",[203,1957,230],{"class":209},[203,1959,1960],{"class":311},"implied",[203,1962,279],{"class":209},[203,1964,1965],{"class":205,"line":654},[203,1966,730],{"class":209},[203,1968,1969],{"class":205,"line":669},[203,1970,561],{"class":209},[203,1972,1973,1975],{"class":205,"line":710},[203,1974,945],{"class":522},[203,1976,1977],{"class":311}," out\n",[203,1979,1980],{"class":205,"line":727},[203,1981,285],{"class":209},[203,1983,1984],{"class":205,"line":733},[203,1985,292],{"emptyLinePlaceholder":291},[203,1987,1988,1990,1992,1994,1996,1998,2000,2002,2004,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2028,2030,2032,2034,2036],{"class":205,"line":764},[203,1989,382],{"class":209},[203,1991,262],{"class":209},[203,1993,387],{"class":233},[203,1995,390],{"class":213},[203,1997,259],{"class":209},[203,1999,395],{"class":226},[203,2001,230],{"class":209},[203,2003,234],{"class":233},[203,2005,237],{"class":213},[203,2007,240],{"class":209},[203,2009,53],{"class":213},[203,2011,245],{"class":209},[203,2013,248],{"class":233},[203,2015,251],{"class":213},[203,2017,240],{"class":209},[203,2019,256],{"class":213},[203,2021,259],{"class":209},[203,2023,262],{"class":209},[203,2025,265],{"class":213},[203,2027,240],{"class":209},[203,2029,270],{"class":213},[203,2031,245],{"class":209},[203,2033,276],{"class":275},[203,2035,259],{"class":209},[203,2037,220],{"class":209},[203,2039,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058,2060,2062,2064,2066],{"class":205,"line":791},[203,2041,1288],{"class":311},[203,2043,245],{"class":209},[203,2045,443],{"class":311},[203,2047,446],{"class":209},[203,2049,449],{"class":311},[203,2051,240],{"class":209},[203,2053,1301],{"class":226},[203,2055,230],{"class":209},[203,2057,234],{"class":311},[203,2059,245],{"class":209},[203,2061,248],{"class":311},[203,2063,240],{"class":209},[203,2065,513],{"class":226},[203,2067,516],{"class":209},[203,2069,2070,2072,2074,2076,2078],{"class":205,"line":825},[203,2071,523],{"class":522},[203,2073,443],{"class":311},[203,2075,528],{"class":209},[203,2077,531],{"class":209},[203,2079,220],{"class":209},[203,2081,2082,2084,2086,2088,2090,2092],{"class":205,"line":830},[203,2083,539],{"class":522},[203,2085,251],{"class":213},[203,2087,240],{"class":209},[203,2089,270],{"class":213},[203,2091,548],{"class":209},[203,2093,724],{"class":311},[203,2095,2096],{"class":205,"line":872},[203,2097,561],{"class":209},[203,2099,2100,2102,2104,2106,2108,2110,2112,2115,2117,2119,2121,2123],{"class":205,"line":899},[203,2101,1288],{"class":311},[203,2103,240],{"class":209},[203,2105,799],{"class":311},[203,2107,802],{"class":209},[203,2109,1746],{"class":226},[203,2111,230],{"class":209},[203,2113,2114],{"class":311},"hierarchy",[203,2116,245],{"class":209},[203,2118,948],{"class":311},[203,2120,240],{"class":209},[203,2122,815],{"class":311},[203,2124,279],{"class":209},[203,2126,2127,2129,2131,2133],{"class":205,"line":932},[203,2128,945],{"class":522},[203,2130,948],{"class":311},[203,2132,245],{"class":209},[203,2134,1398],{"class":209},[203,2136,2137],{"class":205,"line":937},[203,2138,285],{"class":209},[178,2140,2141,2142,2145,2146,2148],{},"Now ",[182,2143,2144],{},"ezz.Role(\"admin\")"," also admits a ",[182,2147,1647],{},", because the resolver returned the expanded set.",[178,2150,2151,2152,2155,2156,2159,2160,2163,2164,2167,2168,2171,2172,2175,2176,2179,2180,2167,2183,2185],{},"Two ready-made hierarchies exist. ",[182,2153,2154],{},"auth.DefaultWebAppHierarchy()"," wires ",[182,2157,2158],{},"super_admin → admin → moderator → user → guest"," plus ",[182,2161,2162],{},"owner → admin",", ",[182,2165,2166],{},"manager → user",", and ",[182,2169,2170],{},"editor → viewer",". ",[182,2173,2174],{},"auth.NewCommonRoleHierarchy()"," is a smaller starting point. Both return a ",[182,2177,2178],{},"*Hierarchy"," you can extend with ",[182,2181,2182],{},"AddRoleImplication",[182,2184,1942],{}," follows implications transitively while guarding against cycles.",[335,2187,2189],{"id":2188},"check-roles-inside-a-handler","Check roles inside a handler",[178,2191,2192],{},"Rules cover whole routes. For finer decisions, ask the user value:",[194,2194,2196],{"className":196,"code":2195,"language":198,"meta":199,"style":199},"app.GET(\"\u002Forders\u002F{id}\", ezz.H(func(ctx *ezz.Context, req GetOrderRequest) (*Order, error) {\n    user := ctx.User().(auth.User)\n\n    order, err := store.Find(ctx, req.ID)\n    if err != nil {\n        return nil, ezz.NotFound(\"order not found\")\n    }\n\n    if order.CustomerID != user.ID() {\n        if rc, ok := user.(interface{ HasRole(string) bool }); !ok || !rc.HasRole(\"support\") {\n            return nil, ezz.NotFound(\"order not found\")\n        }\n    }\n    return order, nil\n}))\n",[182,2197,2198,2261,2286,2290,2323,2335,2360,2364,2368,2392,2456,2478,2482,2486,2496],{"__ignoreMap":199},[203,2199,2200,2202,2204,2206,2208,2210,2213,2215,2217,2219,2221,2224,2227,2229,2231,2233,2235,2237,2239,2242,2245,2247,2250,2253,2255,2257,2259],{"class":205,"line":206},[203,2201,1052],{"class":311},[203,2203,240],{"class":209},[203,2205,1057],{"class":226},[203,2207,230],{"class":209},[203,2209,747],{"class":209},[203,2211,2212],{"class":473},"\u002Forders\u002F{id}",[203,2214,747],{"class":209},[203,2216,245],{"class":209},[203,2218,1026],{"class":311},[203,2220,240],{"class":209},[203,2222,2223],{"class":226},"H",[203,2225,2226],{"class":209},"(func(",[203,2228,234],{"class":233},[203,2230,1754],{"class":209},[203,2232,1080],{"class":213},[203,2234,240],{"class":209},[203,2236,53],{"class":213},[203,2238,245],{"class":209},[203,2240,2241],{"class":233}," req",[203,2243,2244],{"class":213}," GetOrderRequest",[203,2246,259],{"class":209},[203,2248,2249],{"class":209}," (*",[203,2251,2252],{"class":213},"Order",[203,2254,245],{"class":209},[203,2256,276],{"class":275},[203,2258,259],{"class":209},[203,2260,220],{"class":209},[203,2262,2263,2266,2268,2271,2273,2275,2278,2280,2282,2284],{"class":205,"line":223},[203,2264,2265],{"class":311},"    user ",[203,2267,446],{"class":209},[203,2269,2270],{"class":311}," ctx",[203,2272,240],{"class":209},[203,2274,256],{"class":226},[203,2276,2277],{"class":209},"().(",[203,2279,265],{"class":213},[203,2281,240],{"class":209},[203,2283,256],{"class":213},[203,2285,279],{"class":209},[203,2287,2288],{"class":205,"line":282},[203,2289,292],{"emptyLinePlaceholder":291},[203,2291,2292,2295,2297,2299,2301,2304,2306,2309,2311,2313,2315,2317,2319,2321],{"class":205,"line":288},[203,2293,2294],{"class":311},"    order",[203,2296,245],{"class":209},[203,2298,443],{"class":311},[203,2300,446],{"class":209},[203,2302,2303],{"class":311}," store",[203,2305,240],{"class":209},[203,2307,2308],{"class":226},"Find",[203,2310,230],{"class":209},[203,2312,234],{"class":311},[203,2314,245],{"class":209},[203,2316,2241],{"class":311},[203,2318,240],{"class":209},[203,2320,513],{"class":311},[203,2322,279],{"class":209},[203,2324,2325,2327,2329,2331,2333],{"class":205,"line":295},[203,2326,523],{"class":522},[203,2328,443],{"class":311},[203,2330,528],{"class":209},[203,2332,531],{"class":209},[203,2334,220],{"class":209},[203,2336,2337,2339,2342,2344,2346,2349,2351,2353,2356,2358],{"class":205,"line":308},[203,2338,539],{"class":522},[203,2340,2341],{"class":209}," nil,",[203,2343,1026],{"class":311},[203,2345,240],{"class":209},[203,2347,2348],{"class":226},"NotFound",[203,2350,230],{"class":209},[203,2352,747],{"class":209},[203,2354,2355],{"class":473},"order not found",[203,2357,747],{"class":209},[203,2359,279],{"class":209},[203,2361,2362],{"class":205,"line":321},[203,2363,561],{"class":209},[203,2365,2366],{"class":205,"line":331},[203,2367,292],{"emptyLinePlaceholder":291},[203,2369,2370,2372,2375,2377,2380,2382,2384,2386,2388,2390],{"class":205,"line":492},[203,2371,523],{"class":522},[203,2373,2374],{"class":311}," order",[203,2376,240],{"class":209},[203,2378,2379],{"class":311},"CustomerID ",[203,2381,528],{"class":209},[203,2383,248],{"class":311},[203,2385,240],{"class":209},[203,2387,513],{"class":226},[203,2389,638],{"class":209},[203,2391,220],{"class":209},[203,2393,2394,2396,2399,2401,2404,2406,2408,2411,2414,2416,2418,2420,2423,2426,2428,2431,2434,2436,2439,2441,2443,2445,2447,2450,2452,2454],{"class":205,"line":498},[203,2395,672],{"class":522},[203,2397,2398],{"class":311}," rc",[203,2400,245],{"class":209},[203,2402,2403],{"class":311}," ok ",[203,2405,446],{"class":209},[203,2407,248],{"class":311},[203,2409,2410],{"class":209},".(interface{",[203,2412,2413],{"class":226}," HasRole",[203,2415,230],{"class":209},[203,2417,613],{"class":275},[203,2419,259],{"class":209},[203,2421,2422],{"class":275}," bool",[203,2424,2425],{"class":209}," });",[203,2427,738],{"class":209},[203,2429,2430],{"class":311},"ok ",[203,2432,2433],{"class":209},"||",[203,2435,738],{"class":209},[203,2437,2438],{"class":311},"rc",[203,2440,240],{"class":209},[203,2442,1180],{"class":226},[203,2444,230],{"class":209},[203,2446,747],{"class":209},[203,2448,2449],{"class":473},"support",[203,2451,747],{"class":209},[203,2453,259],{"class":209},[203,2455,220],{"class":209},[203,2457,2458,2460,2462,2464,2466,2468,2470,2472,2474,2476],{"class":205,"line":519},[203,2459,713],{"class":522},[203,2461,2341],{"class":209},[203,2463,1026],{"class":311},[203,2465,240],{"class":209},[203,2467,2348],{"class":226},[203,2469,230],{"class":209},[203,2471,747],{"class":209},[203,2473,2355],{"class":473},[203,2475,747],{"class":209},[203,2477,279],{"class":209},[203,2479,2480],{"class":205,"line":536},[203,2481,730],{"class":209},[203,2483,2484],{"class":205,"line":558},[203,2485,561],{"class":209},[203,2487,2488,2490,2492,2494],{"class":205,"line":564},[203,2489,945],{"class":522},[203,2491,2374],{"class":311},[203,2493,245],{"class":209},[203,2495,1398],{"class":209},[203,2497,2498],{"class":205,"line":581},[203,2499,2500],{"class":209},"}))\n",[178,2502,2503],{},"The resolver runs at most once for the request, so this check is free if a route rule already triggered it.",[178,2505,2506,2507,2509,2510,1010],{},"Because resolution is lazy, a handler-level check can be the first thing to hit the resolver. If it fails, ",[182,2508,1180],{}," returns false and the error is available through ",[182,2511,2512],{},"AuthzError()",[194,2514,2516],{"className":196,"code":2515,"language":198,"meta":199,"style":199},"if au, ok := ctx.User().(*auth.AuthorizedUser); ok {\n    if err := au.AuthzError(); err != nil {\n        return nil, ezz.InternalServerError(\"authorization unavailable\").WithInternal(err)\n    }\n}\n",[182,2517,2518,2554,2580,2614,2618],{"__ignoreMap":199},[203,2519,2520,2523,2526,2528,2530,2532,2534,2536,2538,2541,2543,2545,2548,2550,2552],{"class":205,"line":206},[203,2521,2522],{"class":522},"if",[203,2524,2525],{"class":311}," au",[203,2527,245],{"class":209},[203,2529,2403],{"class":311},[203,2531,446],{"class":209},[203,2533,2270],{"class":311},[203,2535,240],{"class":209},[203,2537,256],{"class":226},[203,2539,2540],{"class":209},"().(*",[203,2542,265],{"class":213},[203,2544,240],{"class":209},[203,2546,2547],{"class":213},"AuthorizedUser",[203,2549,699],{"class":209},[203,2551,2403],{"class":311},[203,2553,1907],{"class":209},[203,2555,2556,2558,2560,2562,2564,2566,2569,2572,2574,2576,2578],{"class":205,"line":223},[203,2557,523],{"class":522},[203,2559,443],{"class":311},[203,2561,446],{"class":209},[203,2563,2525],{"class":311},[203,2565,240],{"class":209},[203,2567,2568],{"class":226},"AuthzError",[203,2570,2571],{"class":209},"();",[203,2573,443],{"class":311},[203,2575,528],{"class":209},[203,2577,531],{"class":209},[203,2579,220],{"class":209},[203,2581,2582,2584,2586,2588,2590,2593,2595,2597,2600,2602,2604,2607,2609,2612],{"class":205,"line":282},[203,2583,539],{"class":522},[203,2585,2341],{"class":209},[203,2587,1026],{"class":311},[203,2589,240],{"class":209},[203,2591,2592],{"class":226},"InternalServerError",[203,2594,230],{"class":209},[203,2596,747],{"class":209},[203,2598,2599],{"class":473},"authorization unavailable",[203,2601,747],{"class":209},[203,2603,995],{"class":209},[203,2605,2606],{"class":226},"WithInternal",[203,2608,230],{"class":209},[203,2610,2611],{"class":311},"err",[203,2613,279],{"class":209},[203,2615,2616],{"class":205,"line":288},[203,2617,561],{"class":209},[203,2619,2620],{"class":205,"line":295},[203,2621,285],{"class":209},[335,2623,2625],{"id":2624},"test-a-resolver","Test a resolver",[178,2627,2628],{},"Resolvers are plain functions of a user, so they test without HTTP:",[194,2630,2632],{"className":196,"code":2631,"language":198,"meta":199,"style":199},"func TestResolverGrantsAdmin(t *testing.T) {\n    r := resolver{db: testDB(t)}\n    user := auth.NewBasicUser(&auth.Claims{Subject: \"u-1\"})\n\n    authz, err := r.Resolve(context.Background(), user)\n    if err != nil {\n        t.Fatal(err)\n    }\n    if !slices.Contains(authz.Roles, \"admin\") {\n        t.Fatalf(\"expected admin, got %v\", authz.Roles)\n    }\n}\n",[182,2633,2634,2660,2685,2723,2727,2761,2773,2789,2793,2827,2859,2863],{"__ignoreMap":199},[203,2635,2636,2638,2641,2643,2646,2648,2651,2653,2656,2658],{"class":205,"line":206},[203,2637,382],{"class":209},[203,2639,2640],{"class":226}," TestResolverGrantsAdmin",[203,2642,230],{"class":209},[203,2644,2645],{"class":233},"t",[203,2647,1754],{"class":209},[203,2649,2650],{"class":213},"testing",[203,2652,240],{"class":209},[203,2654,2655],{"class":213},"T",[203,2657,259],{"class":209},[203,2659,220],{"class":209},[203,2661,2662,2665,2667,2669,2671,2673,2675,2678,2680,2682],{"class":205,"line":223},[203,2663,2664],{"class":311},"    r ",[203,2666,446],{"class":209},[203,2668,353],{"class":213},[203,2670,1005],{"class":209},[203,2672,454],{"class":311},[203,2674,1010],{"class":209},[203,2676,2677],{"class":226}," testDB",[203,2679,230],{"class":209},[203,2681,2645],{"class":311},[203,2683,2684],{"class":209},")}\n",[203,2686,2687,2689,2691,2693,2695,2698,2700,2702,2704,2707,2709,2712,2714,2716,2719,2721],{"class":205,"line":282},[203,2688,2265],{"class":311},[203,2690,446],{"class":209},[203,2692,251],{"class":311},[203,2694,240],{"class":209},[203,2696,2697],{"class":226},"NewBasicUser",[203,2699,686],{"class":209},[203,2701,265],{"class":213},[203,2703,240],{"class":209},[203,2705,2706],{"class":213},"Claims",[203,2708,1005],{"class":209},[203,2710,2711],{"class":311},"Subject",[203,2713,1010],{"class":209},[203,2715,1667],{"class":209},[203,2717,2718],{"class":473},"u-1",[203,2720,747],{"class":209},[203,2722,1016],{"class":209},[203,2724,2725],{"class":205,"line":288},[203,2726,292],{"emptyLinePlaceholder":291},[203,2728,2729,2731,2733,2735,2737,2739,2741,2744,2746,2749,2751,2754,2757,2759],{"class":205,"line":295},[203,2730,1288],{"class":311},[203,2732,245],{"class":209},[203,2734,443],{"class":311},[203,2736,446],{"class":209},[203,2738,449],{"class":311},[203,2740,240],{"class":209},[203,2742,2743],{"class":226},"Resolve",[203,2745,230],{"class":209},[203,2747,2748],{"class":311},"context",[203,2750,240],{"class":209},[203,2752,2753],{"class":226},"Background",[203,2755,2756],{"class":209},"(),",[203,2758,248],{"class":311},[203,2760,279],{"class":209},[203,2762,2763,2765,2767,2769,2771],{"class":205,"line":308},[203,2764,523],{"class":522},[203,2766,443],{"class":311},[203,2768,528],{"class":209},[203,2770,531],{"class":209},[203,2772,220],{"class":209},[203,2774,2775,2778,2780,2783,2785,2787],{"class":205,"line":321},[203,2776,2777],{"class":311},"        t",[203,2779,240],{"class":209},[203,2781,2782],{"class":226},"Fatal",[203,2784,230],{"class":209},[203,2786,2611],{"class":311},[203,2788,279],{"class":209},[203,2790,2791],{"class":205,"line":331},[203,2792,561],{"class":209},[203,2794,2795,2797,2799,2802,2804,2807,2809,2811,2813,2815,2817,2819,2821,2823,2825],{"class":205,"line":492},[203,2796,523],{"class":522},[203,2798,738],{"class":209},[203,2800,2801],{"class":311},"slices",[203,2803,240],{"class":209},[203,2805,2806],{"class":226},"Contains",[203,2808,230],{"class":209},[203,2810,810],{"class":311},[203,2812,240],{"class":209},[203,2814,815],{"class":311},[203,2816,245],{"class":209},[203,2818,1667],{"class":209},[203,2820,1092],{"class":473},[203,2822,747],{"class":209},[203,2824,259],{"class":209},[203,2826,220],{"class":209},[203,2828,2829,2831,2833,2836,2838,2840,2843,2847,2849,2851,2853,2855,2857],{"class":205,"line":498},[203,2830,2777],{"class":311},[203,2832,240],{"class":209},[203,2834,2835],{"class":226},"Fatalf",[203,2837,230],{"class":209},[203,2839,747],{"class":209},[203,2841,2842],{"class":473},"expected admin, got ",[203,2844,2846],{"class":2845},"swJcz","%v",[203,2848,747],{"class":209},[203,2850,245],{"class":209},[203,2852,948],{"class":311},[203,2854,240],{"class":209},[203,2856,815],{"class":311},[203,2858,279],{"class":209},[203,2860,2861],{"class":205,"line":519},[203,2862,561],{"class":209},[203,2864,2865],{"class":205,"line":536},[203,2866,285],{"class":209},[2868,2869,2870,1184,2873,2876,2877,1184,2880,2883],"note",{},[182,2871,2872],{},"WithRoleExtractor",[182,2874,2875],{},"WithPermissionChecker"," on the JWT provider belong to an earlier design and are not consulted during authentication. Role and permission checks go through the resolver, or through the token claims when no resolver is set. The ",[182,2878,2879],{},"RoleExtractor",[182,2881,2882],{},"PermissionChecker"," types remain useful as building blocks inside your own resolver.",[335,2885,2887],{"id":2886},"next-steps","Next steps",[2889,2890,2891,2899],"ul",{},[2892,2893,2894,2898],"li",{},[2895,2896,2897],"a",{"href":95},"Audit logging"," to record what was allowed and denied.",[2892,2900,2901,2904],{},[2895,2902,2903],{"href":106},"GoTrue proxy"," if GoTrue issues your tokens.",[2906,2907,2908],"style",{},"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 pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}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}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}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 .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}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 .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 .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":199,"searchDepth":223,"depth":223,"links":2910},[2911,2912,2913,2914,2915,2916,2917,2918,2919],{"id":337,"depth":223,"text":338},{"id":967,"depth":223,"text":968},{"id":1150,"depth":223,"text":1151},{"id":1218,"depth":223,"text":1219},{"id":1424,"depth":223,"text":1425},{"id":1605,"depth":223,"text":1606},{"id":2188,"depth":223,"text":2189},{"id":2624,"depth":223,"text":2625},{"id":2886,"depth":223,"text":2887},"Load a caller's roles and permissions from your own data with an authorization resolver, and cache the result.","md",null,{},{"icon":92},{"title":89,"description":2920},"riV8l_MJaJ40YaX_uf_tpjqzelOGFqNxP5QVMSZZ_Tg",[2928,2930],{"title":84,"path":85,"stem":86,"description":2929,"icon":87,"children":-1},"Issue and validate HS256 tokens, validate GoTrue tokens, or plug in your own credential scheme.",{"title":94,"path":95,"stem":96,"description":2931,"icon":97,"children":-1},"Record authentication and authorization events through a pluggable audit logger.",1784970045269]