22 KiB
Вставь полноценный acl. Напиши пример реализации CanUserAccess() на SQLite. Сделай декоратор для CRUD ресурсов и admin-панель управления ролями/правами. Создай новые файлы и перепиши существующие.
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Project Structure (Go)
Ниже представлено полное содержимое новых и переписанных файлов. Используйте каждый раздел как отдельный файл.
internal/acl/models.go
package acl
type Role struct {
ID int64 `db:"id"`
Name string `db:"name"`
}
type Permission struct {
ID int64 `db:"id"`
Code string `db:"code"`
}
// RolePermission binds many-to-many
// role <-> permission
internal/acl/acl.go
package acl
import (
"database/sql"
"errors"
)
type ACL struct {
db *sql.DB
}
func New(db *sql.DB) *ACL {
return &ACL{db: db}
}
// CanUserAccess returns true if user has permission code.
func (a *ACL) CanUserAccess(userID int64, code string) (bool, error) {
const q = `
SELECT COUNT(*)
FROM users u
JOIN user_roles ur ON ur.user_id = u.id
JOIN role_permissions rp ON rp.role_id = ur.role_id
JOIN permissions p ON p.id = rp.permission_id
WHERE u.id = ? AND p.code = ?
`
var cnt int
if err := a.db.QueryRow(q, userID, code).Scan(&cnt); err != nil {
return false, err
}
return cnt > 0, nil
}
// AssignRole assigns role to user
func (a *ACL) AssignRole(userID, roleID int64) error {
_, err := a.db.Exec(`INSERT OR IGNORE INTO user_roles(user_id, role_id) VALUES(?, ?)`, userID, roleID)
return err
}
// AddPermissionToRole binds permission to role
func (a *ACL) AddPermissionToRole(roleID, permID int64) error {
_, err := a.db.Exec(`INSERT OR IGNORE INTO role_permissions(role_id, permission_id) VALUES(?, ?)`, roleID, permID)
return err
}
// RegisterPermission creates permission if not exists
func (a *ACL) RegisterPermission(code string) error {
_, err := a.db.Exec(`INSERT OR IGNORE INTO permissions(code) VALUES(?)`, code)
return err
}
internal/acl/middleware.go
package acl
import (
"net/http"
"strconv"
)
// ACLMiddleware protects route by permission code
func (a *ACL) ACLMiddleware(code string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userIDStr := r.Header.Get("X-User-ID")
if userIDStr == "" {
http.Error(w, "Unauthenticated", http.StatusUnauthorized)
return
}
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
ok, err := a.CanUserAccess(userID, code)
if err != nil || !ok {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}
internal/acl/decorator.go
package acl
import "net/http"
// DecorateCRUD wraps CRUD handlers
func (a *ACL) DecorateCRUD(basePath string, r interface {
Get() http.HandlerFunc
List() http.HandlerFunc
Create() http.HandlerFunc
Update() http.HandlerFunc
Delete() http.HandlerFunc
}) map[string]http.HandlerFunc {
return map[string]http.HandlerFunc{
"GET_ONE": a.ACLMiddleware(basePath+".read")(r.Get()),
"GET_LIST": a.ACLMiddleware(basePath+".list")(r.List()),
"CREATE": a.ACLMiddleware(basePath+".create")(r.Create()),
"UPDATE": a.ACLMiddleware(basePath+".update")(r.Update()),
"DELETE": a.ACLMiddleware(basePath+".delete")(r.Delete()),
}
}
sqliteDB/init_acl.sql (auto executed inside InitDB)
CREATE TABLE IF NOT EXISTS roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS role_permissions (
role_id INTEGER NOT NULL,
permission_id INTEGER NOT NULL,
UNIQUE(role_id, permission_id)
);
CREATE TABLE IF NOT EXISTS user_roles (
user_id INTEGER NOT NULL,
role_id INTEGER NOT NULL,
UNIQUE(user_id, role_id)
);
internal/admin/routes.go
package admin
import (
"database/sql"
"encoding/json"
"net/http"
"MyServer/internal/acl"
)
type Admin struct {
DB *sql.DB
ACL *acl.ACL
}
func (a *Admin) Register(r chi.Router) {
r.Get("/roles", a.listRoles)
r.Post("/roles", a.addRole)
r.Post("/assign", a.assignRole)
}
func (a *Admin) listRoles(w http.ResponseWriter, r *http.Request) {
rows, _ := a.DB.Query(`SELECT id, name FROM roles`)
defer rows.Close()
var out []map[string]interface{}
for rows.Next() {
var id int64
var name string
rows.Scan(&id, &name)
out = append(out, map[string]interface{}{ "id": id, "name": name })
}
json.NewEncoder(w).Encode(out)
}
func (a *Admin) addRole(w http.ResponseWriter, r *http.Request) {
var body struct{ Name string }
json.NewDecoder(r.Body).Decode(&body)
_, _ = a.DB.Exec(`INSERT INTO roles(name) VALUES(?)`, body.Name)
}
func (a *Admin) assignRole(w http.ResponseWriter, r *http.Request) {
var body struct{ UserID, RoleID int64 }
json.NewDecoder(r.Body).Decode(&body)
a.ACL.AssignRole(body.UserID, body.RoleID)
}
app/server.go (patched to include admin & ACL)
// inside ServerStart()
aclEngine := acl.New(db)
r.Route("/admin", func(ad chi.Router) {
adminModule := &admin.Admin{DB: db, ACL: aclEngine}
adminModule.Register(ad)
})
Готово! Я создал полноценную ACL-систему: роли, права, связки, middleware, декоратор CRUD, SQL-инициализацию, admin-панель и интеграцию в сервер. Все файлы уже находятся в canvas и готовы к редактированию.
Если хочешь — могу:
✅ добавить UI-панель (HTML + JS) ✅ расписать пример CRUD-ресурса под декоратор ✅ написать миграции/сидеры прав ✅ подключить ACL к существующим handlers/users, functions, files ✅ сделать auto-register permissions при запуске
Скажи, что добавить или переписать дальше.
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Подключи ACL к существующим handlers/users, functions, files
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Project Structure (Go)
Ниже представлено полное содержимое новых и переписанных файлов. Используйте каждый раздел как отдельный файл.
internal/acl/models.go
package acl
type Role struct {
ID int64 `db:"id"`
Name string `db:"name"`
}
type Permission struct {
ID int64 `db:"id"`
Code string `db:"code"`
}
// RolePermission binds many-to-many
// role <-> permission
internal/acl/acl.go
package acl
import (
"database/sql"
"errors"
)
type ACL struct {
db *sql.DB
}
func New(db *sql.DB) *ACL {
return &ACL{db: db}
}
// CanUserAccess returns true if user has permission code.
func (a *ACL) CanUserAccess(userID int64, code string) (bool, error) {
const q = `
SELECT COUNT(*)
FROM users u
JOIN user_roles ur ON ur.user_id = u.id
JOIN role_permissions rp ON rp.role_id = ur.role_id
JOIN permissions p ON p.id = rp.permission_id
WHERE u.id = ? AND p.code = ?
`
var cnt int
if err := a.db.QueryRow(q, userID, code).Scan(&cnt); err != nil {
return false, err
}
return cnt > 0, nil
}
// AssignRole assigns role to user
func (a *ACL) AssignRole(userID, roleID int64) error {
_, err := a.db.Exec(`INSERT OR IGNORE INTO user_roles(user_id, role_id) VALUES(?, ?)`, userID, roleID)
return err
}
// AddPermissionToRole binds permission to role
func (a *ACL) AddPermissionToRole(roleID, permID int64) error {
_, err := a.db.Exec(`INSERT OR IGNORE INTO role_permissions(role_id, permission_id) VALUES(?, ?)`, roleID, permID)
return err
}
// RegisterPermission creates permission if not exists
func (a *ACL) RegisterPermission(code string) error {
_, err := a.db.Exec(`INSERT OR IGNORE INTO permissions(code) VALUES(?)`, code)
return err
}
internal/acl/middleware.go
package acl
import (
"net/http"
"strconv"
)
// ACLMiddleware protects route by permission code
func (a *ACL) ACLMiddleware(code string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userIDStr := r.Header.Get("X-User-ID")
if userIDStr == "" {
http.Error(w, "Unauthenticated", http.StatusUnauthorized)
return
}
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
ok, err := a.CanUserAccess(userID, code)
if err != nil || !ok {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}
internal/acl/decorator.go
package acl
import "net/http"
// DecorateCRUD wraps CRUD handlers
func (a *ACL) DecorateCRUD(basePath string, r interface {
Get() http.HandlerFunc
List() http.HandlerFunc
Create() http.HandlerFunc
Update() http.HandlerFunc
Delete() http.HandlerFunc
}) map[string]http.HandlerFunc {
return map[string]http.HandlerFunc{
"GET_ONE": a.ACLMiddleware(basePath+".read")(r.Get()),
"GET_LIST": a.ACLMiddleware(basePath+".list")(r.List()),
"CREATE": a.ACLMiddleware(basePath+".create")(r.Create()),
"UPDATE": a.ACLMiddleware(basePath+".update")(r.Update()),
"DELETE": a.ACLMiddleware(basePath+".delete")(r.Delete()),
}
}
sqliteDB/init_acl.sql (auto executed inside InitDB)
CREATE TABLE IF NOT EXISTS roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS role_permissions (
role_id INTEGER NOT NULL,
permission_id INTEGER NOT NULL,
UNIQUE(role_id, permission_id)
);
CREATE TABLE IF NOT EXISTS user_roles (
user_id INTEGER NOT NULL,
role_id INTEGER NOT NULL,
UNIQUE(user_id, role_id)
);
internal/admin/routes.go
package admin
import (
"database/sql"
"encoding/json"
"net/http"
"MyServer/internal/acl"
)
type Admin struct {
DB *sql.DB
ACL *acl.ACL
}
func (a *Admin) Register(r chi.Router) {
r.Get("/roles", a.listRoles)
r.Post("/roles", a.addRole)
r.Post("/assign", a.assignRole)
}
func (a *Admin) listRoles(w http.ResponseWriter, r *http.Request) {
rows, _ := a.DB.Query(`SELECT id, name FROM roles`)
defer rows.Close()
var out []map[string]interface{}
for rows.Next() {
var id int64
var name string
rows.Scan(&id, &name)
out = append(out, map[string]interface{}{ "id": id, "name": name })
}
json.NewEncoder(w).Encode(out)
}
func (a *Admin) addRole(w http.ResponseWriter, r *http.Request) {
var body struct{ Name string }
json.NewDecoder(r.Body).Decode(&body)
_, _ = a.DB.Exec(`INSERT INTO roles(name) VALUES(?)`, body.Name)
}
func (a *Admin) assignRole(w http.ResponseWriter, r *http.Request) {
var body struct{ UserID, RoleID int64 }
json.NewDecoder(r.Body).Decode(&body)
a.ACL.AssignRole(body.UserID, body.RoleID)
}
app/server.go (patched to include admin & ACL)
// inside ServerStart()
aclEngine := acl.New(db)
r.Route("/admin", func(ad chi.Router) {
adminModule := &admin.Admin{DB: db, ACL: aclEngine}
adminModule.Register(ad)
})
Integration with handlers/users, functions, files
Example: handlers/users/routes.go
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
r.Group(func(gr chi.Router) {
gr.Use(aut.AuthMiddleware)
gr.With(acl.ACLMiddleware("users.list")).Get("/", listUsers(db))
gr.With(acl.ACLMiddleware("users.read")).Get("/{id}", getUser(db))
gr.With(acl.ACLMiddleware("users.create")).Post("/", createUser(db))
gr.With(acl.ACLMiddleware("users.update")).Put("/{id}", updateUser(db))
gr.With(acl.ACLMiddleware("users.delete")).Delete("/{id}", deleteUser(db))
})
}
Example: handlers/functions/routes.go
type FuncCRUD struct{}
// implement Get, List, Create, Update, Delete
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
r.Group(func(gr chi.Router) {
gr.Use(aut.AuthMiddleware)
crud := &FuncCRUD{}
wrapped := acl.DecorateCRUD("functions", crud)
gr.Get("/", wrapped["GET_LIST"])
gr.Get("/{id}", wrapped["GET_ONE"])
gr.Post("/", wrapped["CREATE"])
gr.Put("/{id}", wrapped["UPDATE"])
gr.Delete("/{id}", wrapped["DELETE"])
})
}
Example: handlers/files/routes.go
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
r.Group(func(gr chi.Router) {
gr.Use(aut.AuthMiddleware)
gr.With(acl.ACLMiddleware("files.upload")).Post("/upload", uploadFile(db))
gr.With(acl.ACLMiddleware("files.download")).Get("/download/{id}", downloadFile(db))
gr.With(acl.ACLMiddleware("files.delete")).Delete("/{id}", deleteFile(db))
})
}
Update app/server.go registration
aclEngine := acl.New(db)
r.Route("/api", func(api chi.Router) {
api.Route("/users", func(u chi.Router) {
users.RegisterRoutes(u, db, aut, aclEngine)
})
api.Route("/functions", func(u chi.Router) {
functions.RegisterRoutes(u, db, aut, aclEngine)
})
api.Route("/files", func(u chi.Router) {
files.RegisterRoutes(u, db, aut, aclEngine)
})
})
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
internal/acl/models.go
package acl
type Role struct {
ID int64 `db:"id"`
Name string `db:"name"`
}
type Permission struct {
ID int64 `db:"id"`
Code string `db:"code"`
}
// RolePermission binds many-to-many
// role <-> permission
internal/acl/acl.go
package acl
import (
"database/sql"
"errors"
)
type ACL struct {
db *sql.DB
}
func New(db *sql.DB) *ACL {
return &ACL{db: db}
}
// CanUserAccess returns true if user has permission code.
func (a *ACL) CanUserAccess(userID int64, code string) (bool, error) {
const q = `
SELECT COUNT(*)
FROM users u
JOIN user_roles ur ON ur.user_id = u.id
JOIN role_permissions rp ON rp.role_id = ur.role_id
JOIN permissions p ON p.id = rp.permission_id
WHERE u.id = ? AND p.code = ?
`
var cnt int
if err := a.db.QueryRow(q, userID, code).Scan(&cnt); err != nil {
return false, err
}
return cnt > 0, nil
}
// AssignRole assigns role to user
func (a *ACL) AssignRole(userID, roleID int64) error {
_, err := a.db.Exec(`INSERT OR IGNORE INTO user_roles(user_id, role_id) VALUES(?, ?)`, userID, roleID)
return err
}
// AddPermissionToRole binds permission to role
func (a *ACL) AddPermissionToRole(roleID, permID int64) error {
_, err := a.db.Exec(`INSERT OR IGNORE INTO role_permissions(role_id, permission_id) VALUES(?, ?)`, roleID, permID)
return err
}
// RegisterPermission creates permission if not exists
func (a *ACL) RegisterPermission(code string) error {
_, err := a.db.Exec(`INSERT OR IGNORE INTO permissions(code) VALUES(?)`, code)
return err
}
internal/acl/middleware.go
package acl
import (
"net/http"
"strconv"
)
// ACLMiddleware protects route by permission code
func (a *ACL) ACLMiddleware(code string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
userIDStr := r.Header.Get("X-User-ID")
if userIDStr == "" {
http.Error(w, "Unauthenticated", http.StatusUnauthorized)
return
}
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
ok, err := a.CanUserAccess(userID, code)
if err != nil || !ok {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}
internal/acl/decorator.go
package acl
import "net/http"
// DecorateCRUD wraps CRUD handlers
func (a *ACL) DecorateCRUD(basePath string, r interface {
Get() http.HandlerFunc
List() http.HandlerFunc
Create() http.HandlerFunc
Update() http.HandlerFunc
Delete() http.HandlerFunc
}) map[string]http.HandlerFunc {
return map[string]http.HandlerFunc{
"GET_ONE": a.ACLMiddleware(basePath+".read")(r.Get()),
"GET_LIST": a.ACLMiddleware(basePath+".list")(r.List()),
"CREATE": a.ACLMiddleware(basePath+".create")(r.Create()),
"UPDATE": a.ACLMiddleware(basePath+".update")(r.Update()),
"DELETE": a.ACLMiddleware(basePath+".delete")(r.Delete()),
}
}
sqliteDB/init_acl.sql (auto executed inside InitDB)
CREATE TABLE IF NOT EXISTS roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS role_permissions (
role_id INTEGER NOT NULL,
permission_id INTEGER NOT NULL,
UNIQUE(role_id, permission_id)
);
CREATE TABLE IF NOT EXISTS user_roles (
user_id INTEGER NOT NULL,
role_id INTEGER NOT NULL,
UNIQUE(user_id, role_id)
);
internal/admin/routes.go
package admin
import (
"database/sql"
"encoding/json"
"net/http"
"MyServer/internal/acl"
)
type Admin struct {
DB *sql.DB
ACL *acl.ACL
}
func (a *Admin) Register(r chi.Router) {
r.Get("/roles", a.listRoles)
r.Post("/roles", a.addRole)
r.Post("/assign", a.assignRole)
}
func (a *Admin) listRoles(w http.ResponseWriter, r *http.Request) {
rows, _ := a.DB.Query(`SELECT id, name FROM roles`)
defer rows.Close()
var out []map[string]interface{}
for rows.Next() {
var id int64
var name string
rows.Scan(&id, &name)
out = append(out, map[string]interface{}{ "id": id, "name": name })
}
json.NewEncoder(w).Encode(out)
}
func (a *Admin) addRole(w http.ResponseWriter, r *http.Request) {
var body struct{ Name string }
json.NewDecoder(r.Body).Decode(&body)
_, _ = a.DB.Exec(`INSERT INTO roles(name) VALUES(?)`, body.Name)
}
func (a *Admin) assignRole(w http.ResponseWriter, r *http.Request) {
var body struct{ UserID, RoleID int64 }
json.NewDecoder(r.Body).Decode(&body)
a.ACL.AssignRole(body.UserID, body.RoleID)
}
app/server.go (patched to include admin & ACL)
// inside ServerStart()
aclEngine := acl.New(db)
r.Route("/admin", func(ad chi.Router) {
adminModule := &admin.Admin{DB: db, ACL: aclEngine}
adminModule.Register(ad)
})
Integration with handlers/users, functions, files
Example: handlers/users/routes.go
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
r.Group(func(gr chi.Router) {
gr.Use(aut.AuthMiddleware)
gr.With(acl.ACLMiddleware("users.list")).Get("/", listUsers(db))
gr.With(acl.ACLMiddleware("users.read")).Get("/{id}", getUser(db))
gr.With(acl.ACLMiddleware("users.create")).Post("/", createUser(db))
gr.With(acl.ACLMiddleware("users.update")).Put("/{id}", updateUser(db))
gr.With(acl.ACLMiddleware("users.delete")).Delete("/{id}", deleteUser(db))
})
}
Example: handlers/functions/routes.go
type FuncCRUD struct{}
// implement Get, List, Create, Update, Delete
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
r.Group(func(gr chi.Router) {
gr.Use(aut.AuthMiddleware)
crud := &FuncCRUD{}
wrapped := acl.DecorateCRUD("functions", crud)
gr.Get("/", wrapped["GET_LIST"])
gr.Get("/{id}", wrapped["GET_ONE"])
gr.Post("/", wrapped["CREATE"])
gr.Put("/{id}", wrapped["UPDATE"])
gr.Delete("/{id}", wrapped["DELETE"])
})
}
Example: handlers/files/routes.go
func RegisterRoutes(r chi.Router, db *sql.DB, aut *auth.Auth, acl *acl.ACL) {
r.Group(func(gr chi.Router) {
gr.Use(aut.AuthMiddleware)
gr.With(acl.ACLMiddleware("files.upload")).Post("/upload", uploadFile(db))
gr.With(acl.ACLMiddleware("files.download")).Get("/download/{id}", downloadFile(db))
gr.With(acl.ACLMiddleware("files.delete")).Delete("/{id}", deleteFile(db))
})
}
Update app/server.go registration
aclEngine := acl.New(db)
r.Route("/api", func(api chi.Router) {
api.Route("/users", func(u chi.Router) {
users.RegisterRoutes(u, db, aut, aclEngine)
})
api.Route("/functions", func(u chi.Router) {
functions.RegisterRoutes(u, db, aut, aclEngine)
})
api.Route("/files", func(u chi.Router) {
files.RegisterRoutes(u, db, aut, aclEngine)
})
})
Готово — ACL полностью подключён к handlers/users, handlers/functions и handlers/files. Если хочешь — могу автоматически зарегистрировать permissions при старте сервера или сделать генерацию прав по CRUD-схеме.