diff --git a/cookbook/casbin/server.go b/cookbook/casbin/server.go
index de19d25a..99bd771c 100644
--- a/cookbook/casbin/server.go
+++ b/cookbook/casbin/server.go
@@ -3,6 +3,7 @@ package main
import (
"log/slog"
"net/http"
+ "os"
"github.com/casbin/casbin/v3"
"github.com/golang-jwt/jwt/v5"
@@ -10,6 +11,7 @@ import (
"github.com/labstack/echo/v5"
)
+// docs:start middleware
// NewCasbinMiddleware returns middleware for [Casbin](https://casbin.org/).
func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
@@ -28,6 +30,8 @@ func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Contex
}
}
+// docs:end middleware
+
/*
Test with:
curl -v "http://localhost:8080/dataset1/any" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
@@ -35,10 +39,13 @@ curl -v "http://localhost:8080/dataset1/any" -H "Authorization: Bearer eyJhbGciO
func main() {
e := echo.New()
+ // docs:start enforcer
ce, err := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
if err != nil {
slog.Error("failed to initialize Casbin enforcer", "error", err)
+ os.Exit(1)
}
+ // docs:end enforcer
// BasicAuth middleware does authentication
// - should pass `curl -v -u "alice:password" http://localhost:8080/dataset1/any`
@@ -53,6 +60,7 @@ func main() {
//}
//e.Use(NewCasbinMiddleware(ce, basicAuthUser)) // Casbin does authorization
+ // docs:start jwt
e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication
jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization
token, err := echo.ContextGet[*jwt.Token](c, "user")
@@ -62,6 +70,7 @@ func main() {
return token.Claims.GetSubject()
}
e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization
+ // docs:end jwt
e.GET("/*", func(c *echo.Context) error {
return c.String(http.StatusOK, "Hello, World!")
diff --git a/cookbook/hello-world/server.go b/cookbook/hello-world/server.go
index 3903bf1c..d2a8d493 100644
--- a/cookbook/hello-world/server.go
+++ b/cookbook/hello-world/server.go
@@ -10,6 +10,7 @@ import (
func main() {
// Echo instance
+ // docs:start hero
e := echo.New()
// Middleware
@@ -20,6 +21,7 @@ func main() {
e.GET("/", func(c *echo.Context) error {
return c.String(http.StatusOK, "Hello, World!\n")
})
+ // docs:end hero
// Start server
sc := echo.StartConfig{Address: ":1323"}
diff --git a/site/plugins/remark-source-code.mjs b/site/plugins/remark-source-code.mjs
index 7c1e9b00..278644eb 100644
--- a/site/plugins/remark-source-code.mjs
+++ b/site/plugins/remark-source-code.mjs
@@ -66,7 +66,12 @@ function readSnippet(specifier, root) {
} catch (error) {
throw new Error(`Cannot include ${path}: ${error.message}`);
}
- const lines = source.split('\n');
+ return sourceSnippet(source, region, path);
+}
+
+/** Extract the same source regions from Vite raw imports and Markdown includes. */
+export function sourceSnippet(source, region, path = 'source') {
+ const lines = source.replace(/\r\n/g, '\n').split('\n');
if (region !== undefined) {
const starts = [];
const ends = [];
diff --git a/site/plugins/remark-source-code.test.mjs b/site/plugins/remark-source-code.test.mjs
index 423a25f1..19da7cda 100644
--- a/site/plugins/remark-source-code.test.mjs
+++ b/site/plugins/remark-source-code.test.mjs
@@ -5,7 +5,7 @@ import { join } from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import { unified } from 'unified';
-import remarkSourceCode, { sourceReference, withSourceCode } from './remark-source-code.mjs';
+import remarkSourceCode, { sourceReference, sourceSnippet, withSourceCode } from './remark-source-code.mjs';
import { parseSourceDocument, visitCode } from './source-document.mjs';
import { localePrefixes } from '../src/locales.mjs';
@@ -212,3 +212,24 @@ test('every cookbook page in every locale displays source-owned programs and exc
}
}
});
+
+test('raw component imports and Markdown includes share source excerpts; Casbin middleware appears once per locale', () => {
+ const root = fileURLToPath(new URL('../..', import.meta.url));
+ const path = 'cookbook/hello-world/server.go';
+ const code = sourceSnippet(readFileSync(join(root, path), 'utf8'), 'hero', path);
+ const [included] = programFences(render(`\x60\x60\x60go file=${path}#hero\n\x60\x60\x60`, root));
+ assert.equal(code, included.value);
+ assert.match(code, /e\.GET\("\/"/);
+ assert.doesNotMatch(code, /docs:(start|end)/);
+ for (const locale of localePrefixes) {
+ const page = join(root, 'site/src/content/docs', locale, 'middleware/casbin-auth.md');
+ const rendered = render(readFileSync(page, 'utf8'), root, page);
+ const fences = programFences(rendered);
+ const declarations = fences.flatMap((fence) => [...fence.value.matchAll(/^func NewCasbinMiddleware\b/gm)]);
+ assert.equal(declarations.length, 1, `${locale}Casbin middleware has a single source-backed definition`);
+ assert.ok(fences.some((fence) => fence.value.includes('enforcer.Enforce(')));
+ assert.ok(fences.some((fence) => fence.value.includes('casbin.NewEnforcer(')), `${locale}the enforcer used by the excerpts is defined`);
+ assert.ok(fences.some((fence) => fence.value.includes('echojwt.JWT(')));
+ assert.ok(!fences.some((fence) => /^package main\b/m.test(fence.value)), 'the full server is linked rather than duplicated');
+ }
+});
diff --git a/site/src/components/HomeHero.astro b/site/src/components/HomeHero.astro
index faff83ea..ddd6514b 100644
--- a/site/src/components/HomeHero.astro
+++ b/site/src/components/HomeHero.astro
@@ -1,10 +1,16 @@
---
// Living Terminal hero: code window + a terminal pane that types `curl`
-// and streams Echo's JSON response. Animation is client-side, with a
-// static final-state fallback for reduced-motion / no-JS.
+// and shows the runnable example's response. Animation is client-side, with a
+// static final-state fallback for reduced motion.
import { starsLabel } from '../data/github.ts';
import stable from '../generated/echo-source.json';
+import { Code } from 'astro:components';
+import helloWorldSource from '../../../cookbook/hello-world/server.go?raw';
+import { sourceSnippet } from '../../plugins/remark-source-code.mjs';
const next = process.env.DOCS_CHANNEL === 'next';
+const helloWorldCode = sourceSnippet(helloWorldSource, 'hero', 'cookbook/hello-world/server.go');
+// The code window is aria-hidden, so Shiki's
must not be a tab stop.
+const notFocusable = { pre(node: { properties: Record }) { delete node.properties.tabindex; } };
---
@@ -28,19 +34,9 @@ const next = process.env.DOCS_CHANNEL === 'next';
- main.go
+ server.go · excerpt
-
package main
-
-import "github.com/labstack/echo/v5"
-
-func main() {
- e := echo.New()
- e.GET("/", func(c *echo.Context) error {
- return c.JSON(200, map[string]string{"message": "Hello, World!"})
- })
- e.Start(":1323")
-}
+
~/echo $
@@ -77,8 +73,8 @@ const next = process.env.DOCS_CHANNEL === 'next';
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
const response =
- '
HTTP/1.1 200 OK · 0 allocations\n' +
- '
{ "message": "Hello, World!" }';
+ '
HTTP/1.1 200 OK\n' +
+ '
Hello, World!';
const cmd = 'curl localhost:1323';
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
diff --git a/site/src/content/docs/es/middleware/casbin-auth.md b/site/src/content/docs/es/middleware/casbin-auth.md
index d90c98f2..5b085ec1 100644
--- a/site/src/content/docs/es/middleware/casbin-auth.md
+++ b/site/src/content/docs/es/middleware/casbin-auth.md
@@ -26,11 +26,15 @@ Consulta el [resumen de API](https://casbin.org/docs/api-overview) y la
```bash
go get github.com/casbin/casbin/v3
+go get github.com/labstack/echo-jwt/v5
+go get github.com/golang-jwt/jwt/v5
```
```go
import (
"github.com/casbin/casbin/v3"
+ "github.com/golang-jwt/jwt/v5"
+ echojwt "github.com/labstack/echo-jwt/v5"
)
```
@@ -39,58 +43,24 @@ import (
Echo no incluye un middleware Casbin; la integración es un wrapper pequeño alrededor del
enforcer de Casbin:
-```go
-// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/).
-func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc {
- return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c *echo.Context) error {
- username, err := userGetter(c)
- if err != nil {
- return echo.ErrUnauthorized.Wrap(err)
- }
- if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil {
- return echo.ErrInternalServerError.Wrap(err)
- } else if !pass {
- return echo.NewHTTPError(http.StatusForbidden, "access denied")
- }
- return next(c)
- }
- }
-}
+```go file=cookbook/casbin/server.go#middleware
```
## Ejemplo
Crea un archivo de modelo Casbin `auth_model.conf`:
-```ini
-[request_definition]
-r = sub, obj, act
-
-[policy_definition]
-p = sub, obj, act
-
-[role_definition]
-g = _, _
+```ini file=cookbook/casbin/auth_model.conf
+```
-[policy_effect]
-e = some(where (p.eft == allow))
+Crea un archivo de policy Casbin `auth_policy.csv`:
-[matchers]
-m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
+```csv file=cookbook/casbin/auth_policy.csv
```
-Crea un archivo de policy Casbin `auth_policy.csv`:
+Carga el modelo y la policy en un enforcer de Casbin:
-```csv
-p, 1234567890, /dataset1/*, GET
-p, alice, /dataset1/*, GET
-p, alice, /dataset1/resource1, POST
-p, bob, /dataset2/resource1, *
-p, bob, /dataset2/resource2, GET
-p, bob, /dataset2/folder1/*, POST
-p, dataset1_admin, /dataset1/*, *
-g, cathy, dataset1_admin
+```go file=cookbook/casbin/server.go#enforcer
```
La autenticación y la autorización son responsabilidades separadas. Autentica al usuario con
@@ -99,16 +69,7 @@ pueda autorizar el request.
### Con JWT
-```go
-e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication
-jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization
- token, err := echo.ContextGet[*jwt.Token](c, "user")
- if err != nil {
- return "", err
- }
- return token.Claims.GetSubject()
-}
-e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization
+```go file=cookbook/casbin/server.go#jwt
```
Pruébalo con:
@@ -143,61 +104,4 @@ curl -v -u "alice:password" http://localhost:8080/dataset2/resource2
### Ejemplo completo de Casbin + JWT
-```go
-package main
-
-import (
- "log/slog"
- "net/http"
-
- "github.com/casbin/casbin/v3"
- "github.com/golang-jwt/jwt/v5"
- echojwt "github.com/labstack/echo-jwt/v5"
- "github.com/labstack/echo/v5"
-)
-
-// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/).
-func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc {
- return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c *echo.Context) error {
- username, err := userGetter(c)
- if err != nil {
- return echo.ErrUnauthorized.Wrap(err)
- }
- if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil {
- return echo.ErrInternalServerError.Wrap(err)
- } else if !pass {
- return echo.NewHTTPError(http.StatusForbidden, "access denied")
- }
- return next(c)
- }
- }
-}
-
-func main() {
- e := echo.New()
-
- ce, err := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
- if err != nil {
- slog.Error("failed to initialize Casbin enforcer", "error", err)
- }
-
- e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication
- jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization
- token, err := echo.ContextGet[*jwt.Token](c, "user")
- if err != nil {
- return "", err
- }
- return token.Claims.GetSubject()
- }
- e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization
-
- e.GET("/*", func(c *echo.Context) error {
- return c.String(http.StatusOK, "Hello, World!")
- })
-
- if err := e.Start(":8080"); err != nil {
- e.Logger.Error("failed to start server", "error", err)
- }
-}
-```
+Ejecuta el [ejemplo completo de Casbin + JWT](https://github.com/labstack/echox/tree/master/cookbook/casbin) con los archivos de modelo y política anteriores.
diff --git a/site/src/content/docs/ja/middleware/casbin-auth.md b/site/src/content/docs/ja/middleware/casbin-auth.md
index 5b18fe8f..aff3ea6e 100644
--- a/site/src/content/docs/ja/middleware/casbin-auth.md
+++ b/site/src/content/docs/ja/middleware/casbin-auth.md
@@ -26,11 +26,15 @@ sidebar:
```bash
go get github.com/casbin/casbin/v3
+go get github.com/labstack/echo-jwt/v5
+go get github.com/golang-jwt/jwt/v5
```
```go
import (
"github.com/casbin/casbin/v3"
+ "github.com/golang-jwt/jwt/v5"
+ echojwt "github.com/labstack/echo-jwt/v5"
)
```
@@ -38,58 +42,24 @@ import (
Echo には Casbin ミドルウェアは同梱されていません。この連携は Casbin enforcer の小さなラッパーです。
-```go
-// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/).
-func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc {
- return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c *echo.Context) error {
- username, err := userGetter(c)
- if err != nil {
- return echo.ErrUnauthorized.Wrap(err)
- }
- if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil {
- return echo.ErrInternalServerError.Wrap(err)
- } else if !pass {
- return echo.NewHTTPError(http.StatusForbidden, "access denied")
- }
- return next(c)
- }
- }
-}
+```go file=cookbook/casbin/server.go#middleware
```
## 例
Casbin モデルファイル `auth_model.conf` を作成します。
-```ini
-[request_definition]
-r = sub, obj, act
-
-[policy_definition]
-p = sub, obj, act
-
-[role_definition]
-g = _, _
+```ini file=cookbook/casbin/auth_model.conf
+```
-[policy_effect]
-e = some(where (p.eft == allow))
+Casbin ポリシーファイル `auth_policy.csv` を作成します。
-[matchers]
-m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
+```csv file=cookbook/casbin/auth_policy.csv
```
-Casbin ポリシーファイル `auth_policy.csv` を作成します。
+モデルとポリシーを Casbin enforcer に読み込みます。
-```csv
-p, 1234567890, /dataset1/*, GET
-p, alice, /dataset1/*, GET
-p, alice, /dataset1/resource1, POST
-p, bob, /dataset2/resource1, *
-p, bob, /dataset2/resource2, GET
-p, bob, /dataset2/folder1/*, POST
-p, dataset1_admin, /dataset1/*, *
-g, cathy, dataset1_admin
+```go file=cookbook/casbin/server.go#enforcer
```
認証と認可は別の関心事です。JWT や Basic Auth など別のミドルウェアでユーザーを認証し、
@@ -97,16 +67,7 @@ Casbin がリクエストを認可できるよう `userGetter` を渡します
### JWT と使う
-```go
-e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication
-jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization
- token, err := echo.ContextGet[*jwt.Token](c, "user")
- if err != nil {
- return "", err
- }
- return token.Claims.GetSubject()
-}
-e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization
+```go file=cookbook/casbin/server.go#jwt
```
次で試します。
@@ -141,61 +102,4 @@ curl -v -u "alice:password" http://localhost:8080/dataset2/resource2
### Casbin + JWT の完全な例
-```go
-package main
-
-import (
- "log/slog"
- "net/http"
-
- "github.com/casbin/casbin/v3"
- "github.com/golang-jwt/jwt/v5"
- echojwt "github.com/labstack/echo-jwt/v5"
- "github.com/labstack/echo/v5"
-)
-
-// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/).
-func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc {
- return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c *echo.Context) error {
- username, err := userGetter(c)
- if err != nil {
- return echo.ErrUnauthorized.Wrap(err)
- }
- if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil {
- return echo.ErrInternalServerError.Wrap(err)
- } else if !pass {
- return echo.NewHTTPError(http.StatusForbidden, "access denied")
- }
- return next(c)
- }
- }
-}
-
-func main() {
- e := echo.New()
-
- ce, err := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
- if err != nil {
- slog.Error("failed to initialize Casbin enforcer", "error", err)
- }
-
- e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication
- jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization
- token, err := echo.ContextGet[*jwt.Token](c, "user")
- if err != nil {
- return "", err
- }
- return token.Claims.GetSubject()
- }
- e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization
-
- e.GET("/*", func(c *echo.Context) error {
- return c.String(http.StatusOK, "Hello, World!")
- })
-
- if err := e.Start(":8080"); err != nil {
- e.Logger.Error("failed to start server", "error", err)
- }
-}
-```
+上記のモデルとポリシーファイルを使って、[Casbin + JWT の完全な例](https://github.com/labstack/echox/tree/master/cookbook/casbin)を実行できます。
diff --git a/site/src/content/docs/middleware/casbin-auth.md b/site/src/content/docs/middleware/casbin-auth.md
index 7012b66b..6b44134e 100644
--- a/site/src/content/docs/middleware/casbin-auth.md
+++ b/site/src/content/docs/middleware/casbin-auth.md
@@ -26,11 +26,15 @@ See the [API overview](https://casbin.org/docs/api-overview) and the
```bash
go get github.com/casbin/casbin/v3
+go get github.com/labstack/echo-jwt/v5
+go get github.com/golang-jwt/jwt/v5
```
```go
import (
"github.com/casbin/casbin/v3"
+ "github.com/golang-jwt/jwt/v5"
+ echojwt "github.com/labstack/echo-jwt/v5"
)
```
@@ -39,58 +43,24 @@ import (
Echo does not ship a Casbin middleware; the integration is a small wrapper around the
Casbin enforcer:
-```go
-// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/).
-func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc {
- return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c *echo.Context) error {
- username, err := userGetter(c)
- if err != nil {
- return echo.ErrUnauthorized.Wrap(err)
- }
- if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil {
- return echo.ErrInternalServerError.Wrap(err)
- } else if !pass {
- return echo.NewHTTPError(http.StatusForbidden, "access denied")
- }
- return next(c)
- }
- }
-}
+```go file=cookbook/casbin/server.go#middleware
```
## Example
Create a Casbin model file `auth_model.conf`:
-```ini
-[request_definition]
-r = sub, obj, act
-
-[policy_definition]
-p = sub, obj, act
-
-[role_definition]
-g = _, _
+```ini file=cookbook/casbin/auth_model.conf
+```
-[policy_effect]
-e = some(where (p.eft == allow))
+Create a Casbin policy file `auth_policy.csv`:
-[matchers]
-m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
+```csv file=cookbook/casbin/auth_policy.csv
```
-Create a Casbin policy file `auth_policy.csv`:
+Load the model and policy into a Casbin enforcer:
-```csv
-p, 1234567890, /dataset1/*, GET
-p, alice, /dataset1/*, GET
-p, alice, /dataset1/resource1, POST
-p, bob, /dataset2/resource1, *
-p, bob, /dataset2/resource2, GET
-p, bob, /dataset2/folder1/*, POST
-p, dataset1_admin, /dataset1/*, *
-g, cathy, dataset1_admin
+```go file=cookbook/casbin/server.go#enforcer
```
Authentication and authorization are separate concerns. Authenticate the user with
@@ -99,16 +69,7 @@ can authorize the request.
### With JWT
-```go
-e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication
-jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization
- token, err := echo.ContextGet[*jwt.Token](c, "user")
- if err != nil {
- return "", err
- }
- return token.Claims.GetSubject()
-}
-e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization
+```go file=cookbook/casbin/server.go#jwt
```
Try it with:
@@ -143,61 +104,4 @@ curl -v -u "alice:password" http://localhost:8080/dataset2/resource2
### Full Casbin + JWT example
-```go
-package main
-
-import (
- "log/slog"
- "net/http"
-
- "github.com/casbin/casbin/v3"
- "github.com/golang-jwt/jwt/v5"
- echojwt "github.com/labstack/echo-jwt/v5"
- "github.com/labstack/echo/v5"
-)
-
-// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/).
-func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc {
- return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c *echo.Context) error {
- username, err := userGetter(c)
- if err != nil {
- return echo.ErrUnauthorized.Wrap(err)
- }
- if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil {
- return echo.ErrInternalServerError.Wrap(err)
- } else if !pass {
- return echo.NewHTTPError(http.StatusForbidden, "access denied")
- }
- return next(c)
- }
- }
-}
-
-func main() {
- e := echo.New()
-
- ce, err := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
- if err != nil {
- slog.Error("failed to initialize Casbin enforcer", "error", err)
- }
-
- e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication
- jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization
- token, err := echo.ContextGet[*jwt.Token](c, "user")
- if err != nil {
- return "", err
- }
- return token.Claims.GetSubject()
- }
- e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization
-
- e.GET("/*", func(c *echo.Context) error {
- return c.String(http.StatusOK, "Hello, World!")
- })
-
- if err := e.Start(":8080"); err != nil {
- e.Logger.Error("failed to start server", "error", err)
- }
-}
-```
+Run the [complete Casbin + JWT example](https://github.com/labstack/echox/tree/master/cookbook/casbin), using the model and policy files above.
diff --git a/site/src/content/docs/pt-br/middleware/casbin-auth.md b/site/src/content/docs/pt-br/middleware/casbin-auth.md
index 4d7ae7a0..235ee03e 100644
--- a/site/src/content/docs/pt-br/middleware/casbin-auth.md
+++ b/site/src/content/docs/pt-br/middleware/casbin-auth.md
@@ -26,11 +26,15 @@ Veja a [visão geral da API](https://casbin.org/docs/api-overview) e a
```bash
go get github.com/casbin/casbin/v3
+go get github.com/labstack/echo-jwt/v5
+go get github.com/golang-jwt/jwt/v5
```
```go
import (
"github.com/casbin/casbin/v3"
+ "github.com/golang-jwt/jwt/v5"
+ echojwt "github.com/labstack/echo-jwt/v5"
)
```
@@ -39,58 +43,24 @@ import (
Echo não inclui um middleware Casbin; a integração é um pequeno wrapper em torno do
enforcer do Casbin:
-```go
-// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/).
-func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc {
- return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c *echo.Context) error {
- username, err := userGetter(c)
- if err != nil {
- return echo.ErrUnauthorized.Wrap(err)
- }
- if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil {
- return echo.ErrInternalServerError.Wrap(err)
- } else if !pass {
- return echo.NewHTTPError(http.StatusForbidden, "access denied")
- }
- return next(c)
- }
- }
-}
+```go file=cookbook/casbin/server.go#middleware
```
## Exemplo
Crie um arquivo de modelo Casbin `auth_model.conf`:
-```ini
-[request_definition]
-r = sub, obj, act
-
-[policy_definition]
-p = sub, obj, act
-
-[role_definition]
-g = _, _
+```ini file=cookbook/casbin/auth_model.conf
+```
-[policy_effect]
-e = some(where (p.eft == allow))
+Crie um arquivo de política Casbin `auth_policy.csv`:
-[matchers]
-m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
+```csv file=cookbook/casbin/auth_policy.csv
```
-Crie um arquivo de política Casbin `auth_policy.csv`:
+Carregue o modelo e a política em um enforcer do Casbin:
-```csv
-p, 1234567890, /dataset1/*, GET
-p, alice, /dataset1/*, GET
-p, alice, /dataset1/resource1, POST
-p, bob, /dataset2/resource1, *
-p, bob, /dataset2/resource2, GET
-p, bob, /dataset2/folder1/*, POST
-p, dataset1_admin, /dataset1/*, *
-g, cathy, dataset1_admin
+```go file=cookbook/casbin/server.go#enforcer
```
Autenticação e autorização são preocupações separadas. Autentique o usuário com
@@ -99,16 +69,7 @@ possa autorizar o request.
### Com JWT
-```go
-e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication
-jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization
- token, err := echo.ContextGet[*jwt.Token](c, "user")
- if err != nil {
- return "", err
- }
- return token.Claims.GetSubject()
-}
-e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization
+```go file=cookbook/casbin/server.go#jwt
```
Teste com:
@@ -143,61 +104,4 @@ curl -v -u "alice:password" http://localhost:8080/dataset2/resource2
### Exemplo completo de Casbin + JWT
-```go
-package main
-
-import (
- "log/slog"
- "net/http"
-
- "github.com/casbin/casbin/v3"
- "github.com/golang-jwt/jwt/v5"
- echojwt "github.com/labstack/echo-jwt/v5"
- "github.com/labstack/echo/v5"
-)
-
-// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/).
-func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc {
- return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c *echo.Context) error {
- username, err := userGetter(c)
- if err != nil {
- return echo.ErrUnauthorized.Wrap(err)
- }
- if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil {
- return echo.ErrInternalServerError.Wrap(err)
- } else if !pass {
- return echo.NewHTTPError(http.StatusForbidden, "access denied")
- }
- return next(c)
- }
- }
-}
-
-func main() {
- e := echo.New()
-
- ce, err := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
- if err != nil {
- slog.Error("failed to initialize Casbin enforcer", "error", err)
- }
-
- e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication
- jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization
- token, err := echo.ContextGet[*jwt.Token](c, "user")
- if err != nil {
- return "", err
- }
- return token.Claims.GetSubject()
- }
- e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization
-
- e.GET("/*", func(c *echo.Context) error {
- return c.String(http.StatusOK, "Hello, World!")
- })
-
- if err := e.Start(":8080"); err != nil {
- e.Logger.Error("failed to start server", "error", err)
- }
-}
-```
+Execute o [exemplo completo de Casbin + JWT](https://github.com/labstack/echox/tree/master/cookbook/casbin) com os arquivos de modelo e política acima.
diff --git a/site/src/content/docs/zh-cn/middleware/casbin-auth.md b/site/src/content/docs/zh-cn/middleware/casbin-auth.md
index 3435bb4f..e2109bea 100644
--- a/site/src/content/docs/zh-cn/middleware/casbin-auth.md
+++ b/site/src/content/docs/zh-cn/middleware/casbin-auth.md
@@ -26,11 +26,15 @@ sidebar:
```bash
go get github.com/casbin/casbin/v3
+go get github.com/labstack/echo-jwt/v5
+go get github.com/golang-jwt/jwt/v5
```
```go
import (
"github.com/casbin/casbin/v3"
+ "github.com/golang-jwt/jwt/v5"
+ echojwt "github.com/labstack/echo-jwt/v5"
)
```
@@ -38,58 +42,24 @@ import (
Echo 不自带 Casbin 中间件;该集成是对 Casbin enforcer 的一层小包装:
-```go
-// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/).
-func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc {
- return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c *echo.Context) error {
- username, err := userGetter(c)
- if err != nil {
- return echo.ErrUnauthorized.Wrap(err)
- }
- if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil {
- return echo.ErrInternalServerError.Wrap(err)
- } else if !pass {
- return echo.NewHTTPError(http.StatusForbidden, "access denied")
- }
- return next(c)
- }
- }
-}
+```go file=cookbook/casbin/server.go#middleware
```
## 示例
创建 Casbin 模型文件 `auth_model.conf`:
-```ini
-[request_definition]
-r = sub, obj, act
-
-[policy_definition]
-p = sub, obj, act
-
-[role_definition]
-g = _, _
+```ini file=cookbook/casbin/auth_model.conf
+```
-[policy_effect]
-e = some(where (p.eft == allow))
+创建 Casbin 策略文件 `auth_policy.csv`:
-[matchers]
-m = g(r.sub, p.sub) && keyMatch(r.obj, p.obj) && (r.act == p.act || p.act == "*")
+```csv file=cookbook/casbin/auth_policy.csv
```
-创建 Casbin 策略文件 `auth_policy.csv`:
+将模型和策略加载到 Casbin enforcer:
-```csv
-p, 1234567890, /dataset1/*, GET
-p, alice, /dataset1/*, GET
-p, alice, /dataset1/resource1, POST
-p, bob, /dataset2/resource1, *
-p, bob, /dataset2/resource2, GET
-p, bob, /dataset2/folder1/*, POST
-p, dataset1_admin, /dataset1/*, *
-g, cathy, dataset1_admin
+```go file=cookbook/casbin/server.go#enforcer
```
认证和授权是不同的关注点。使用另一个中间件(如 JWT 或 Basic Auth)认证用户,
@@ -97,16 +67,7 @@ g, cathy, dataset1_admin
### 搭配 JWT
-```go
-e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication
-jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization
- token, err := echo.ContextGet[*jwt.Token](c, "user")
- if err != nil {
- return "", err
- }
- return token.Claims.GetSubject()
-}
-e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization
+```go file=cookbook/casbin/server.go#jwt
```
用下面命令尝试:
@@ -141,61 +102,4 @@ curl -v -u "alice:password" http://localhost:8080/dataset2/resource2
### 完整 Casbin + JWT 示例
-```go
-package main
-
-import (
- "log/slog"
- "net/http"
-
- "github.com/casbin/casbin/v3"
- "github.com/golang-jwt/jwt/v5"
- echojwt "github.com/labstack/echo-jwt/v5"
- "github.com/labstack/echo/v5"
-)
-
-// NewCasbinMiddleware returns middleware for Casbin (https://casbin.org/).
-func NewCasbinMiddleware(enforcer *casbin.Enforcer, userGetter func(*echo.Context) (string, error)) echo.MiddlewareFunc {
- return func(next echo.HandlerFunc) echo.HandlerFunc {
- return func(c *echo.Context) error {
- username, err := userGetter(c)
- if err != nil {
- return echo.ErrUnauthorized.Wrap(err)
- }
- if pass, err := enforcer.Enforce(username, c.Request().URL.Path, c.Request().Method); err != nil {
- return echo.ErrInternalServerError.Wrap(err)
- } else if !pass {
- return echo.NewHTTPError(http.StatusForbidden, "access denied")
- }
- return next(c)
- }
- }
-}
-
-func main() {
- e := echo.New()
-
- ce, err := casbin.NewEnforcer("auth_model.conf", "auth_policy.csv")
- if err != nil {
- slog.Error("failed to initialize Casbin enforcer", "error", err)
- }
-
- e.Use(echojwt.JWT([]byte("secret"))) // JWT middleware does authentication
- jwtUser := func(c *echo.Context) (string, error) { // JWT user getter for Casbin authorization
- token, err := echo.ContextGet[*jwt.Token](c, "user")
- if err != nil {
- return "", err
- }
- return token.Claims.GetSubject()
- }
- e.Use(NewCasbinMiddleware(ce, jwtUser)) // Casbin does authorization
-
- e.GET("/*", func(c *echo.Context) error {
- return c.String(http.StatusOK, "Hello, World!")
- })
-
- if err := e.Start(":8080"); err != nil {
- e.Logger.Error("failed to start server", "error", err)
- }
-}
-```
+使用上面的模型和策略文件运行[完整的 Casbin + JWT 示例](https://github.com/labstack/echox/tree/master/cookbook/casbin)。