{"id":28906853,"url":"https://github.com/hatmahat/go-rbac","last_synced_at":"2026-04-21T16:36:19.153Z","repository":{"id":300253835,"uuid":"1005669905","full_name":"hatmahat/go-rbac","owner":"hatmahat","description":"Lightweight, framework-agnostic RBAC package for Go with context injection, in-memory caching, and GORM support.","archived":false,"fork":false,"pushed_at":"2025-06-20T16:46:33.000Z","size":13,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-06-20T17:40:52.714Z","etag":null,"topics":["authorization","chi","echo","fiber","gin","golang","middleware","rbac","security"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hatmahat.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-20T15:53:25.000Z","updated_at":"2025-06-20T16:50:26.000Z","dependencies_parsed_at":"2025-06-20T17:50:58.393Z","dependency_job_id":null,"html_url":"https://github.com/hatmahat/go-rbac","commit_stats":null,"previous_names":["hatmahat/go-rbac"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hatmahat/go-rbac","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hatmahat%2Fgo-rbac","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hatmahat%2Fgo-rbac/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hatmahat%2Fgo-rbac/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hatmahat%2Fgo-rbac/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hatmahat","download_url":"https://codeload.github.com/hatmahat/go-rbac/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hatmahat%2Fgo-rbac/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32100522,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-21T11:25:29.218Z","status":"ssl_error","status_checked_at":"2026-04-21T11:25:28.499Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authorization","chi","echo","fiber","gin","golang","middleware","rbac","security"],"created_at":"2025-06-21T15:10:17.194Z","updated_at":"2026-04-21T16:36:19.145Z","avatar_url":"https://github.com/hatmahat.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e **Disclaimer:**  \n\u003e I originally wrote this library for a specific internal project.  \n\u003e Later, I refactored and packaged it as a standalone module with the help of LLMs\n\u003e to improve the structure and documentation for reuse and open source sharing.\n\n# go-rbac\n[![Go Reference](https://pkg.go.dev/badge/github.com/hatmahat/go-rbac.svg)](https://pkg.go.dev/github.com/hatmahat/go-rbac/rbac)\n\nA lightweight, framework-agnostic **Role-Based Access Control (RBAC)** library for Go, built with:\n\n- Context-based privilege injection\n- In-memory caching for fast access\n- Optional auto-refresh of role privileges\n- Minimal dependency (works with any HTTP framework or DB driver)\n\n---\n\n## Features\n\n- No framework lock-in (works with Echo, Gin, Chi, Fiber, etc.)\n- Decoupled data layer — bring your own database via PrivilegeRepository \n- Simple API: `HasPrivilege`, `GetRolePrivileges`, `InjectContext`\n- Optional GORM-based implementation provided\n- Includes built-in RBACService with cache and auto-refresh support\n\n---\n\n## Installation\n\n```bash\ngo get github.com/hatmahat/go-rbac/rbac\n```\n\n## Folder Structure\n```\ngo-rbac/\n├── example/                    # Minimal usage example using Echo\n│   └── main.go\n├── rbac/                       # Core RBAC logic (framework-agnostic)\n│   ├── cache.go                # In-memory cache for role privileges\n│   ├── context.go              # Context keys and access helpers\n│   ├── injector.go             # Inject privileges into context\n│   ├── logger.go               # Optional logger (Console or Null)\n│   ├── privilege_repository.go # Interface for custom DB repositories \n│   └── service.go              # Main RBAC service logic\n├── rbacgorm/                   # Optional GORM-based implementation\n│   └── gorm_repository.go\n```\n## RBAC Model: Privileges, Roles, and Users\n\nThis library uses a minimal and flexible RBAC (Role-Based Access Control) model based on three key entities:\n\n| Type      | Description                                      | Example           |\n|-----------|--------------------------------------------------|-------------------|\n| Privilege | A string that defines a specific permission code | `read:users`      |\n| Role      | A group of privileges assigned to a category     | `admin`           |\n| User      | Assigned one or more roles to determine access   | user `123` with role `viewer` |\n\n---\n\n### How it works\n\n- A **Privilege** is a string like `read:compliance`, `delete:report`, etc.\n- A **Role** (e.g., `admin`, `viewer`) contains a list of such privilege codes.\n- A **User** is associated with a role — usually passed in JWT claims or request headers like `X-Role-ID`.\n- At runtime, the role’s privileges are injected into the request `context.Context`.\n- You can check access easily with helpers like:\n\n```go\nif !rbac.HasPrivilegeInContext(ctx, \"read:compliance\") {\n    return errors.New(\"forbidden\")\n}\n```\nYou can use any privilege naming convention (e.g., read:users, manage:projects, export:data).\nThe system treats them as simple string lookups for fast in-memory evaluation.\n\n\n## Quick Start \n### Step 1: Implement your own PrivilegeRepository\n#### Option A: Use the built-in GORM implementation \n```go\nimport (\n\t\"time\"\n\n    \"github.com/hatmahat/go-rbac/rbac\"\n    \"github.com/hatmahat/go-rbac/rbacgorm\"\n)\n\nrepo := rbacgorm.NewGormPrivilegeRepository(db)\nrbacService := rbac.NewRBACService(repo, 5*time.Minute, rbac.NewConsoleLogger()) // optional logger\n```\n#### Option B: Create your own repository (e.g. using database/sql)\n```go\npackage myrepo\n\nimport (\n\t\"context\"\n\t\"database/sql\"\n\t\"fmt\"\n)\n\ntype SQLPrivilegeRepository struct {\n\tdb *sql.DB\n}\n\nfunc NewRepo(db *sql.DB) *SQLPrivilegeRepository {\n\treturn \u0026SQLPrivilegeRepository{db: db}\n}\n\nfunc (r *SQLPrivilegeRepository) FetchPrivilegesByRoleID(ctx context.Context, roleID string) (map[string]bool, error) {\n\tconst query = `\n\t\tSELECT p.code\n\t\tFROM privileges p\n\t\tJOIN role_privileges rp ON p.id = rp.privilege_id\n\t\tWHERE rp.role_id = $1\n\t`\n\n\trows, err := r.db.QueryContext(ctx, query, roleID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"query failed: %w\", err)\n\t}\n\tdefer rows.Close()\n\n\tprivMap := make(map[string]bool)\n\tfor rows.Next() {\n\t\tvar code string\n\t\tif err := rows.Scan(\u0026code); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"scan failed: %w\", err)\n\t\t}\n\t\tprivMap[code] = true\n\t}\n\n\treturn privMap, nil\n}\n```\nUsing your repo in main.go\n```go\npackage main\n\nimport (\n\t\"database/sql\"\n\t\"log\"\n\t\"time\"\n\n\t_ \"github.com/lib/pq\"\n\t\"github.com/hatmahat/go-rbac/rbac\"\n\t\"your_project/myrepo\"\n)\n\nfunc main() {\n\tdb, err := sql.Open(\"postgres\", \"postgres://user:pass@localhost/dbname?sslmode=disable\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\trepo := myrepo.NewRepo(db)\n\n\t// Optional: use NewConsoleLogger() for dev or NewNullLogger() for silence\n\trbacService := rbac.NewRBACService(repo, 5*time.Minute, rbac.NewConsoleLogger())\n\n\t// Use rbacService in your middleware, handlers, etc.\n}\n```\n### Step 2: Inject into context \n#### Examples\n\n#### 1. Echo\n```go\ne.Use(func(next echo.HandlerFunc) echo.HandlerFunc {\n    return func(c echo.Context) error {\n        roleID := c.Request().Header.Get(\"X-Role-ID\")\n        userID := c.Request().Header.Get(\"X-User-ID\")\n\n        privileges, err := rbacService.GetRolePrivileges(c.Request().Context(), roleID)\n        if err != nil {\n            return c.JSON(http.StatusForbidden, map[string]string{\"error\": \"forbidden\"})\n        }\n\n        ctx := rbac.InjectContext(c.Request().Context(), roleID, userID, privileges)\n        c.SetRequest(c.Request().WithContext(ctx))\n        return next(c)\n    }\n})\n```\n\n#### 2. Gin\n```go\nr.Use(func(c *gin.Context) {\n    roleID := c.GetHeader(\"X-Role-ID\")\n    userID := c.GetHeader(\"X-User-ID\")\n\n    privileges, err := rbacService.GetRolePrivileges(c.Request.Context(), roleID)\n    if err != nil {\n        c.AbortWithStatusJSON(http.StatusForbidden, gin.H{\"error\": \"forbidden\"})\n        return\n    }\n\n    ctx := rbac.InjectContext(c.Request.Context(), roleID, userID, privileges)\n    c.Request = c.Request.WithContext(ctx)\n    c.Next()\n})\n```\n\n#### 3. Chi\n```go\nr.Use(func(next http.Handler) http.Handler {\n    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        roleID := r.Header.Get(\"X-Role-ID\")\n        userID := r.Header.Get(\"X-User-ID\")\n\n        privileges, err := rbacService.GetRolePrivileges(r.Context(), roleID)\n        if err != nil {\n            http.Error(w, \"Forbidden\", http.StatusForbidden)\n            return\n        }\n\n        ctx := rbac.InjectContext(r.Context(), roleID, userID, privileges)\n        next.ServeHTTP(w, r.WithContext(ctx))\n    })\n})\n```\n\n#### 4. Fiber\n```go\napp.Use(func(c *fiber.Ctx) error {\n    roleID := c.Get(\"X-Role-ID\")\n    userID := c.Get(\"X-User-ID\")\n\n    privileges, err := rbacService.GetRolePrivileges(c.UserContext(), roleID)\n    if err != nil {\n        return c.Status(fiber.StatusForbidden).JSON(fiber.Map{\"error\": \"forbidden\"})\n    }\n\n    ctx := rbac.InjectContext(c.UserContext(), roleID, userID, privileges)\n    c.SetUserContext(ctx)\n    return c.Next()\n})\n```\n### About RBACService\n\nThe core `RBACService` handles:\n- In-memory caching of privileges per role\n- Auto-refreshing cache (if interval \u003e 0)\n- Fast lookups with `HasPrivilege`, `HasAnyPrivilege`, etc.\n\nYou don't need to manage caching or database access manually — just implement `PrivilegeRepository` and call `NewRBACService(...)`.\n### RBACService Interface\n\nThe `RBACService` is the core entry point for working with roles and privileges. It provides cache-aware methods for checking access and managing privilege data:\n\n| Function | Purpose |\n|----------|-------------|\n| `GetRolePrivileges(ctx, roleID)` | Returns all privilege codes assigned to a given role. Uses in-memory cache if available. |\n| `HasPrivilege(ctx, roleID, privilege)` | Checks whether the given role has a specific privilege. Returns a boolean. |\n| `HasAnyPrivilege(ctx, roleID, codes...)` | Returns `true` if the role has **any** of the specified privilege codes. Useful for OR-checks. |\n| `SetNewRolePrivileges(ctx, roleID, privileges)` | Sets/overrides the cached privileges for a role (used during setup/testing). Does **not** persist to DB. |\n| `DeleteRolePrivileges(ctx, roleID)` | Deletes the privilege cache for a role. Will force a refresh from your DB on next access. |\n\n\u003e All methods auto-refresh from DB if privileges are missing from cache.\n\n## Checking Privileges in Your Handlers\nOnce you’ve injected RBAC context using InjectContext, you can retrieve and use the privileges easily:\n```go\nctx := c.Request().Context()\n\nprivs, ok := rbac.GetPrivilegesFromContext(ctx)\nif !ok || !privs[\"read:compliance\"] {\n    return c.JSON(http.StatusForbidden, map[string]string{\"error\": \"forbidden\"})\n}\n\nuserID, _ := rbac.GetUserIDFromContext(ctx)\nreturn c.JSON(http.StatusOK, map[string]string{\n    \"message\": fmt.Sprintf(\"Hello user %s! You have access.\", userID),\n})\n```\n### Example in Business Logic Layer (Service)\n```go\nfunc (s *YourService) GetData(ctx context.Context) error {\n    if !rbac.HasPrivilegeInContext(ctx, \"read:data\") {\n        return fmt.Errorf(\"forbidden\")\n    }\n\n    userID, _ := rbac.GetUserIDFromContext(ctx)\n    fmt.Println(\"Fetching data for user:\", userID)\n\n    // continue processing...\n}\n```\n\n\n### Built-in Context Helpers:\n\n| Function                                              | Purpose                                                          |\n|-------------------------------------------------------|------------------------------------------------------------------|\n| `rbac.GetPrivilegesFromContext(ctx)`                  | Returns map of granted privileges from context                  |\n| `rbac.HasPrivilegeInContext(ctx, code)`               | Shorthand to check if a specific privilege exists in context    |\n| `rbac.GetUserIDFromContext(ctx)`                      | Retrieves user ID from context (if injected earlier)            |\n| `rbac.GetRoleIDFromContext(ctx)`                      | Retrieves role ID from context (if injected earlier)            |\n| `rbac.InjectContext(ctx, roleID, userID, privileges)` | Injects role ID, user ID, and privileges into request context   |\n\n## Example: Run Locally\n### Step 1: Clone and run the example\n```bash\ngit clone https://github.com/hatmahat/go-rbac.git\ncd go-rbac/example\ngo run main.go\n```\n### Step 2: Test access\n#### Role with Access\n```bash\ncurl -H \"X-Role-ID: admin\" -H \"X-User-ID: 123\" http://localhost:8080/compliance\n```\n#### Response:\n```json\n{\n  \"message\": \"Hello user 123! You have access.\"\n}\n```\n#### Role without Access\n```bash\ncurl -H \"X-Role-ID: guest\" -H \"X-User-ID: 456\" http://localhost:8080/compliance\n```\n#### Response:\n```json\n{\n  \"error\": \"forbidden\"\n}\n```\n\n## Configuring Privileges\nYou can use any data source. If you’re using SQL, this is the expected schema for the GORM example:\n```sql\nCREATE TABLE privileges (\n  id TEXT PRIMARY KEY,\n  code TEXT NOT NULL\n);\n\nCREATE TABLE role_privileges (\n  id TEXT PRIMARY KEY,\n  role_id TEXT NOT NULL,\n  privilege_id TEXT NOT NULL\n);\n```\nOr define your own structure by implementing PrivilegeRepository.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhatmahat%2Fgo-rbac","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhatmahat%2Fgo-rbac","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhatmahat%2Fgo-rbac/lists"}