Skip to content

Commit f26e7c1

Browse files
boilerplate and docs
1 parent 88058b4 commit f26e7c1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+3045
-129
lines changed

README.md

Lines changed: 230 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,230 @@
1-
# go-rbac
2-
go-rbac is a lightweight and scalable library for implementing Role-Based Access Control (RBAC) in Go applications. It provides an intuitive and flexible way to define roles, permissions, and access scopes to secure your APIs and application resources.
1+
# Go-RBAC: Role and Scope Based Access Control Implementation
2+
3+
This repository demonstrates how to implement **Role and Scope-Based Access Control (RBAC)** in a Go application using **Redis**, **MySQL**, and the Echo framework.
4+
5+
<p align="center">
6+
<img src="docs/rbac.png" alt="rbac" />
7+
</p>
8+
9+
---
10+
11+
## Features 🚀
12+
13+
- **Role-Based Access Control**: Restricts access to resources based on user roles (e.g., Admin, Employee, Customer).
14+
- **Scope-Based Permissions**: Adds granular permissions for specific operations (e.g., `users:read`, `users:create`).
15+
- **Redis Integration**: Caches user roles, scopes and blacklisted access.
16+
- **MySQL Integration**: Stores user and role data persistently.
17+
- **Secure Authentication**: Includes endpoints for user registration and login.
18+
19+
---
20+
21+
## Prerequisites 📋
22+
23+
- **Go**: Version 1.21 or higher
24+
- **MySQL**: For storing user data
25+
- **Redis**: For caching role and scope data
26+
- **Docker**: For containerization (optional)
27+
28+
---
29+
30+
## Project Structure 📂
31+
```bash
32+
├── cmd
33+
│ └── main.go # Application entry point
34+
├── config
35+
│ ├── config.go # Configuration loader and management logic
36+
│ └── config.yaml # Configuration file for environment variables and application settings
37+
├── internal
38+
│ ├── {sub_domain} # Grouped by subdomains or modules
39+
│ │ ├── usecase # Application-specific business logic
40+
│ │ │ └── usecase.go # Implementation of use cases for the subdomain
41+
│ │ ├── entities # Core domain entities
42+
│ │ │ └── entity.go # Definitions of core domain entities
43+
│ │ ├── dtos # Data Transfer Objects for request/response payloads
44+
│ │ │ └── dtos.go # DTO definitions for input/output operations
45+
│ │ ├── repository # Persistence and database layer
46+
│ │ │ └── repository.go # Implementation of repository interfaces
47+
│ │ ├── delivery # Delivery layer (e.g., HTTP handlers, routes)
48+
│ │ │ ├── handlers.go # Request/response handlers for the subdomain
49+
│ │ │ └── routes.go # Route definitions for the subdomain
50+
│ │ ├── usecase.go # Interface for the use case layer
51+
│ │ ├── repository.go # Interface for the repository layer
52+
│ │ ├── delivery.go # Interface for the delivery layer
53+
├── middleware # Custom middleware (e.g., RBAC, logging, authentication)
54+
├── pkg # Shared libraries or utility functions
55+
│ ├── redis # Utilities for Redis interactions
56+
│ ├── constants # Application-wide constants and enumerations
57+
│ ├── utils # General utility functions and helpers
58+
│ ├── datasources # Data source configuration and initialization (e.g., MySQL, Redis)
59+
│ └── rbac # Role-based access control utilities and logic
60+
├── migrations # Database migration files
61+
├── infrastructure # Infrastructure setup and configurations
62+
│ └── docker-compose.yml # Docker Compose configuration for service orchestration
63+
├── docs # Documentation (e.g., API specifications, design documents)
64+
├── tests # Testing suite for various layers
65+
│ ├── e2e # End-to-end tests
66+
│ ├── unit # Unit tests
67+
│ └── integration # Integration tests
68+
├── README.md # Project documentation
69+
└── Makefile # Build and automation instructions for the project
70+
```
71+
72+
## Endpoints and Access Requirements 🌐
73+
| Endpoint | HTTP Method | Scope | Roles with Access | Description |
74+
|-------------------|-------------|-----------------|------------------------|------------------------------------------|
75+
| `/users` | `GET` | `users:read` | `Admin`, `Employee` | Retrieve the list of users. |
76+
| `/users/:id` | `PUT` | `users:update` | `Admin`, `Employee` | Update user details. |
77+
| `/users` | `POST` | `users:create` | `Admin` | Create a new user. |
78+
| `/users/:id` | `DELETE` | `users:delete` | `Admin` | Delete a user. |
79+
| `/profile` | `GET` | `profile:read` | `Customer`, `Employee` | Retrieve the authenticated user's profile.|
80+
| `/profile` | `PUT` | `profile:update`| `Customer`, `Employee` | Update the authenticated user's profile. |
81+
---
82+
83+
## Installation & Setup 🛠️
84+
85+
### Clone the Repository
86+
```bash
87+
git clone https://github.com/DoWithLogic/go-rbac.git
88+
cd go-rbac
89+
```
90+
91+
### Run the Application
92+
The make run command will:
93+
- Start the Docker containers for Redis and the database (if not already running).
94+
- Apply any pending database migrations.
95+
- Start the application.
96+
```bash
97+
make run
98+
```
99+
100+
101+
## Example Implementation 🧑‍💻
102+
### Middleware for Role Validation
103+
```bash
104+
func (m *Middleware) RolesMiddleware(roles ...constants.UserRole) echo.MiddlewareFunc {
105+
return func(next echo.HandlerFunc) echo.HandlerFunc {
106+
return func(c echo.Context) error {
107+
jwtData, ok := c.Get(constants.AuthCredentialContextKey.String()).(*security.JWTClaims)
108+
if !ok {
109+
return response.ErrorBuilder(app_errors.Forbidden(app_errors.ErrAccessDenied)).Send(c)
110+
}
111+
112+
if !m.hasRequiredRole(jwtData.Role, roles) {
113+
return response.ErrorBuilder(app_errors.Forbidden(app_errors.ErrAccessDenied)).Send(c)
114+
}
115+
116+
// Store the token claims in the request context for later use
117+
c.Set(constants.AuthCredentialContextKey.String(), jwtData)
118+
119+
return next(c)
120+
}
121+
}
122+
}
123+
124+
func (m *Middleware) hasRequiredRole(userRole constants.UserRole, roles []constants.UserRole) bool {
125+
for _, r := range roles {
126+
if r == userRole {
127+
return true
128+
}
129+
}
130+
return false
131+
}
132+
```
133+
### Middleware for Scope Validation
134+
```bash
135+
func (m *Middleware) PermissionsMiddleware(permissions ...constants.Permission) echo.MiddlewareFunc {
136+
return func(next echo.HandlerFunc) echo.HandlerFunc {
137+
return func(c echo.Context) error {
138+
jwtData, ok := c.Get(constants.AuthCredentialContextKey.String()).(*security.JWTClaims)
139+
if !ok {
140+
return response.ErrorBuilder(app_errors.Forbidden(app_errors.ErrAccessDenied)).Send(c)
141+
}
142+
143+
if !m.hasRequiredPermission(jwtData.Permissions, permissions) {
144+
return response.ErrorBuilder(app_errors.Forbidden(app_errors.ErrAccessDenied)).Send(c)
145+
}
146+
147+
c.Set(constants.AuthCredentialContextKey.String(), jwtData)
148+
149+
return next(c)
150+
}
151+
}
152+
}
153+
154+
func (m *Middleware) hasRequiredPermission(userPermissions, requiredPermissions []constants.Permission) bool {
155+
requiredPermissionsMap := make(map[constants.Permission]bool)
156+
for _, permission := range requiredPermissions {
157+
requiredPermissionsMap[permission] = true
158+
}
159+
160+
for _, permission := range userPermissions {
161+
if requiredPermissionsMap[permission] {
162+
return true
163+
}
164+
}
165+
166+
return false
167+
}
168+
```
169+
170+
### Assign Middleware to Endpoints
171+
```bash
172+
func MapUserRoutes(g echo.Group, h users.Handlers, mw *middlewares.Middleware) {
173+
users := g.Group("/users", mw.JWTMiddleware())
174+
users.POST("", h.CreateUserHandler, mw.RolesMiddleware(constants.AdminUserRole), mw.PermissionsMiddleware(constants.UsersCreatePermission))
175+
}
176+
```
177+
178+
## Configuration ⚙️
179+
```bash
180+
App:
181+
Name: "go-rbac"
182+
Version: "0.0.1"
183+
Scheme: "http"
184+
Host: "localhost:3002"
185+
Environment: local #local,development,staging,production
186+
187+
Server:
188+
Port: "3002"
189+
Debug: true
190+
TimeZone: "Asia/Jakarta"
191+
192+
Database:
193+
Host: "127.0.0.1"
194+
Port: "3306"
195+
DBName: "go_rbac"
196+
UserName: "root"
197+
Password: "pwd"
198+
Debug: true
199+
200+
Security:
201+
JWT:
202+
Key: "95476ff7-c7b2-49d7-853e-322b6f983914"
203+
ExpiredInSecond: 3600
204+
```
205+
206+
## API Documentation 📑
207+
208+
### Overview
209+
This repository provides a set of API endpoints for managing roles, permissions, and user access. The API allows you to create, update, retrieve, and delete roles, permissions, and role-permission mappings. It also supports secure JWT-based authentication to enforce role-based access control.
210+
211+
### Explore Swagger Documentation
212+
For a detailed description of all the available API endpoints, request/response formats, and examples, explore our Swagger documentation at the following link:
213+
214+
- [Swagger Documentation](http://localhost:3002/swagger/index.html)
215+
216+
The Swagger documentation will provide detailed information on:
217+
- **Available Endpoints**: All API routes for managing users, roles, permissions, and access control.
218+
- **Request/Response Formats**: Detailed format for the expected input and output of each API endpoint.
219+
- **Authentication**: How to authenticate requests using JWT tokens.
220+
- **Role and Permission Validation**: How roles and permissions are validated for each endpoint.
221+
222+
## License 📄
223+
This project is licensed under the [MIT License](LICENSE). See the LICENSE file for details.
224+
225+
## Contributing 🤝
226+
Feel free to submit pull requests or open issues to improve this project. Contributions are always welcome!
227+
```bash
228+
This `README.md` file includes the project overview, structure, setup instructions, endpoint details, and example implementations. Let me know if you'd like to add or modify any sections!
229+
```
230+

cmd/cmd.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"strings"
6+
"time"
7+
8+
"github.com/DoWithLogic/go-rbac/config"
9+
"github.com/DoWithLogic/go-rbac/docs"
10+
"github.com/DoWithLogic/go-rbac/internal/server"
11+
"github.com/labstack/gommon/log"
12+
)
13+
14+
func Start() {
15+
// Load the application configuration from the specified directory.
16+
cfg, err := config.LoadConfig("config")
17+
if err != nil {
18+
panic(err)
19+
}
20+
21+
// make swagger host dynamic.
22+
docs.SwaggerInfo.Host = cfg.App.Host
23+
docs.SwaggerInfo.Schemes = strings.Split(cfg.App.Scheme, ",")
24+
docs.SwaggerInfo.Version = cfg.App.Version
25+
26+
if _, err := time.LoadLocation(cfg.Server.TimeZone); err != nil {
27+
panic(err)
28+
}
29+
30+
// Create a new instance of the application and start it.
31+
if err := server.NewServer(context.Background(), cfg).Run(); err != nil {
32+
log.Warn(err)
33+
}
34+
}

cmd/main.go

Lines changed: 0 additions & 5 deletions
This file was deleted.

config/config.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package config
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"log"
7+
"strings"
8+
9+
"github.com/spf13/viper"
10+
)
11+
12+
type (
13+
Config struct {
14+
App AppConfig
15+
Server ServerConfig
16+
Database DatabaseConfig
17+
Security SecurityConfig
18+
Redis RedisConfig
19+
}
20+
21+
// AppConfig holds the configuration related to the application settings.
22+
AppConfig struct {
23+
Name string
24+
Version string
25+
Scheme string
26+
Host string
27+
Environment string
28+
}
29+
30+
// ServerConfig holds the configuration for the server settings.
31+
ServerConfig struct {
32+
Port string // The port on which the server will listen.
33+
Debug bool // Indicates if debug mode is enabled.
34+
TimeZone string // The time zone setting for the server.
35+
}
36+
37+
// DatabaseConfig holds the configuration for the database connection.
38+
DatabaseConfig struct {
39+
Host string // The host address of the database.
40+
Port string // The port number of the database.
41+
DBName string // The name of the database.
42+
UserName string // The username for connecting to the database.
43+
Password string // The password for connecting to the database.
44+
Debug bool // The Debug for debugging when query executed.
45+
}
46+
47+
SecurityConfig struct {
48+
JWT JWTConfig
49+
}
50+
51+
JWTConfig struct {
52+
Key string
53+
ExpiredInSecond int64
54+
Label string
55+
}
56+
57+
RedisConfig struct {
58+
Addr string
59+
Password string
60+
}
61+
)
62+
63+
// LoadConfig loads the configuration from the specified filename.
64+
func LoadConfig(filename string) (Config, error) {
65+
// Create a new Viper instance.
66+
v := viper.New()
67+
68+
// Set the configuration file name, path, and environment variable settings.
69+
v.SetConfigName(fmt.Sprintf("config/%s", filename))
70+
v.AddConfigPath(".")
71+
v.AutomaticEnv()
72+
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
73+
74+
// Read the configuration file.
75+
if err := v.ReadInConfig(); err != nil {
76+
fmt.Printf("Error reading config file: %v\n", err)
77+
return Config{}, err
78+
}
79+
80+
// Unmarshal the configuration into the Config struct.
81+
var config Config
82+
if err := v.Unmarshal(&config); err != nil {
83+
fmt.Printf("Error unmarshaling config: %v\n", err)
84+
return Config{}, err
85+
}
86+
87+
return config, nil
88+
}
89+
90+
// LoadConfigPath loads the configuration from the specified path.
91+
func LoadConfigPath(path string) (Config, error) {
92+
// Create a new Viper instance.
93+
v := viper.New()
94+
95+
// Set the configuration file name, path, and environment variable settings.
96+
v.SetConfigName(path)
97+
v.AddConfigPath(".")
98+
v.AutomaticEnv()
99+
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
100+
101+
// Read the configuration file.
102+
if err := v.ReadInConfig(); err != nil {
103+
// Handle the case where the configuration file is not found.
104+
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
105+
return Config{}, errors.New("config file not found")
106+
}
107+
return Config{}, err
108+
}
109+
110+
// Parse the configuration into the Config struct.
111+
var c Config
112+
if err := v.Unmarshal(&c); err != nil {
113+
log.Printf("unable to decode into struct, %v", err)
114+
return Config{}, err
115+
}
116+
117+
return c, nil
118+
}

0 commit comments

Comments
 (0)