feat(niki): add admin authorization service.

This commit is contained in:
Iman Mirazimi 2024-02-24 20:36:13 +03:30
parent 17b7c4beb2
commit 28ee6babd3
17 changed files with 246 additions and 31 deletions

1
.gitignore vendored
View File

@ -24,4 +24,3 @@ bin
*.env
logs/
mise.log

View File

@ -18,8 +18,4 @@ format:
@golangci-lint run --fix
build:
go build main.go
run-dev:
sudo docker compose up
go build main.go

View File

@ -2,24 +2,28 @@ package adminhandler
import (
adminservice "git.gocasts.ir/ebhomengo/niki/service/admin/admin"
adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization"
adminauthservice "git.gocasts.ir/ebhomengo/niki/service/auth"
adminvalidator "git.gocasts.ir/ebhomengo/niki/validator/admin/admin"
)
type Handler struct {
authConfig adminauthservice.Config
authSvc adminauthservice.Service
adminSvc adminservice.Service
adminVld adminvalidator.Validator
authConfig adminauthservice.Config
authSvc adminauthservice.Service
adminSvc adminservice.Service
adminVld adminvalidator.Validator
adminAuthorizeSvc adminauthorizationservice.Service
}
func New(authConfig adminauthservice.Config, authSvc adminauthservice.Service,
adminSvc adminservice.Service, adminVld adminvalidator.Validator,
adminAuthorizeSvc adminauthorizationservice.Service,
) Handler {
return Handler{
authConfig: authConfig,
authSvc: authSvc,
adminSvc: adminSvc,
adminVld: adminVld,
authConfig: authConfig,
authSvc: authSvc,
adminSvc: adminSvc,
adminVld: adminVld,
adminAuthorizeSvc: adminAuthorizeSvc,
}
}

View File

@ -2,6 +2,7 @@ package adminhandler
import (
"git.gocasts.ir/ebhomengo/niki/delivery/http_server/middleware"
"git.gocasts.ir/ebhomengo/niki/entity"
"github.com/labstack/echo/v4"
)
@ -10,9 +11,8 @@ func (h Handler) SetRoutes(e *echo.Echo) {
//nolint:gocritic
//r.POST("/", h.Add).Name = "admin-addkindboxreq"
r.POST("/register", h.Register)
r.POST("/register", h.Register, middleware.Auth(h.authSvc, h.authConfig), middleware.AdminAuthorization(h.adminAuthorizeSvc, entity.AdminAdminRegisterPermission))
r.POST("/login-by-phone", h.LoginByPhoneNumber)
r.GET("/test", h.LoginByPhoneNumber, middleware.Auth(h.authSvc, h.authConfig))
//nolint:gocritic
//r.PATCH("/:id", h.Update).Name = "admin-updatekindboxreq"
}

View File

@ -1,25 +1,29 @@
package adminkindboxhandler
import (
adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization"
adminkindboxservice "git.gocasts.ir/ebhomengo/niki/service/admin/kind_box"
authservice "git.gocasts.ir/ebhomengo/niki/service/auth"
adminkindboxvalidator "git.gocasts.ir/ebhomengo/niki/validator/admin/kind_box"
)
type Handler struct {
authConfig authservice.Config
authSvc authservice.Service
adminKindBoxSvc adminkindboxservice.Service
adminKindBoxVld adminkindboxvalidator.Validator
authConfig authservice.Config
authSvc authservice.Service
adminKindBoxSvc adminkindboxservice.Service
adminKindBoxVld adminkindboxvalidator.Validator
adminAuthorizeSvc adminauthorizationservice.Service
}
func New(authConfig authservice.Config, authSvc authservice.Service,
adminKindBoxSvc adminkindboxservice.Service, adminKindBoxVld adminkindboxvalidator.Validator,
adminAuthorizeSvc adminauthorizationservice.Service,
) Handler {
return Handler{
authConfig: authConfig,
authSvc: authSvc,
adminKindBoxSvc: adminKindBoxSvc,
adminKindBoxVld: adminKindBoxVld,
authConfig: authConfig,
authSvc: authSvc,
adminKindBoxSvc: adminKindBoxSvc,
adminKindBoxVld: adminKindBoxVld,
adminAuthorizeSvc: adminAuthorizeSvc,
}
}

View File

@ -1,6 +1,7 @@
package adminkindboxreqhandler
import (
adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization"
adminkindboxreqservice "git.gocasts.ir/ebhomengo/niki/service/admin/kind_box_req"
authservice "git.gocasts.ir/ebhomengo/niki/service/auth"
adminkindboxreqvalidator "git.gocasts.ir/ebhomengo/niki/validator/admin/kind_box_req"
@ -11,15 +12,18 @@ type Handler struct {
authSvc authservice.Service
adminKindBoxReqSvc adminkindboxreqservice.Service
adminKindBoxReqVld adminkindboxreqvalidator.Validator
adminAuthorizeSvc adminauthorizationservice.Service
}
func New(authConfig authservice.Config, authSvc authservice.Service,
adminKindBoxReqSvc adminkindboxreqservice.Service, adminKindBoxReqVld adminkindboxreqvalidator.Validator,
adminAuthorizeSvc adminauthorizationservice.Service,
) Handler {
return Handler{
authConfig: authConfig,
authSvc: authSvc,
adminKindBoxReqSvc: adminKindBoxReqSvc,
adminKindBoxReqVld: adminKindBoxReqVld,
adminAuthorizeSvc: adminAuthorizeSvc,
}
}

View File

@ -2,6 +2,7 @@ package adminkindboxreqhandler
import (
"git.gocasts.ir/ebhomengo/niki/delivery/http_server/middleware"
"git.gocasts.ir/ebhomengo/niki/entity"
echo "github.com/labstack/echo/v4"
)
@ -9,9 +10,9 @@ func (h Handler) SetRoutes(e *echo.Echo) {
r := e.Group("/admin/kindboxreqs")
// todo - add acl
r.PATCH("/accept-kind-box-req/:id", h.Accept)
r.PATCH("/reject-kind-box-req/:id", h.Reject)
r.PATCH("/accept-kind-box-req/:id", h.Accept, middleware.Auth(h.authSvc, h.authConfig), middleware.AdminAuthorization(h.adminAuthorizeSvc, entity.AdminKindBoxReqAcceptPermission))
r.PATCH("/reject-kind-box-req/:id", h.Reject, middleware.Auth(h.authSvc, h.authConfig), middleware.AdminAuthorization(h.adminAuthorizeSvc, entity.AdminKindBoxReqRejectPermission))
r.PATCH("/deliver-kind-box-req/:id", h.Deliver)
r.PATCH("/assign-sender-agent/:id", h.AssignSenderAgent)
r.GET("/", h.GetAll, middleware.Auth(h.authSvc, h.authConfig))
r.GET("/", h.GetAll, middleware.Auth(h.authSvc, h.authConfig), middleware.AdminAuthorization(h.adminAuthorizeSvc, entity.AdminKindBoxReqGetAllPermission))
}

View File

@ -0,0 +1,36 @@
package middleware
import (
"net/http"
"git.gocasts.ir/ebhomengo/niki/entity"
"git.gocasts.ir/ebhomengo/niki/pkg/claim"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization"
"github.com/labstack/echo/v4"
)
func AdminAuthorization(service adminauthorizationservice.Service,
permissions ...entity.AdminPermission,
) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) (err error) {
claims := claim.GetClaimsFromEchoContext(c)
isAllowed, err := service.CheckAccess(claims.UserID, entity.MapToAdminRole(claims.Role), permissions...)
if err != nil {
return c.JSON(http.StatusInternalServerError, echo.Map{
"message": errmsg.ErrorMsgSomethingWentWrong,
})
}
if !isAllowed {
return c.JSON(http.StatusForbidden, echo.Map{
"message": errmsg.ErrorMsgUserNotAllowed,
})
}
return next(c)
}
}
}

View File

@ -10,6 +10,7 @@ import (
benefactorhandler "git.gocasts.ir/ebhomengo/niki/delivery/http_server/benefactor/benefactor"
benefactorkindboxreqhandler "git.gocasts.ir/ebhomengo/niki/delivery/http_server/benefactor/kind_box_req"
adminservice "git.gocasts.ir/ebhomengo/niki/service/admin/admin"
adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization"
adminkindboxreqservice "git.gocasts.ir/ebhomengo/niki/service/admin/kind_box_req"
authservice "git.gocasts.ir/ebhomengo/niki/service/auth"
benefactoraddressservice "git.gocasts.ir/ebhomengo/niki/service/benefactor/address"
@ -48,6 +49,7 @@ func New(
adminAuthSvc authservice.Service,
adminKinBoxReqSvc adminkindboxreqservice.Service,
adminKinBoxReqVld adminkindboxreqvalidator.Validator,
adminAuthorizeSvc adminauthorizationservice.Service,
) Server {
return Server{
Router: echo.New(),
@ -55,8 +57,8 @@ func New(
benefactorHandler: benefactorhandler.New(cfg.Auth, benefactorAuthSvc, benefactorSvc, benefactorVld),
benefactorKindBoxReqHandler: benefactorkindboxreqhandler.New(cfg.Auth, benefactorAuthSvc, benefactorKindBoxReqSvc, benefactorKindBoxReqVld),
benefactorAddressHandler: benefactoraddresshandler.New(cfg.Auth, benefactorAuthSvc, benefactorAddressSvc, benefactorAddressVld),
adminHandler: adminhandler.New(cfg.AdminAuth, adminAuthSvc, adminSvc, adminVld),
adminKindBoxReqHandler: adminkindboxreqhandler.New(cfg.Auth, adminAuthSvc, adminKinBoxReqSvc, adminKinBoxReqVld),
adminHandler: adminhandler.New(cfg.AdminAuth, adminAuthSvc, adminSvc, adminVld, adminAuthorizeSvc),
adminKindBoxReqHandler: adminkindboxreqhandler.New(cfg.Auth, adminAuthSvc, adminKinBoxReqSvc, adminKinBoxReqVld, adminAuthorizeSvc),
}
}

View File

@ -0,0 +1,14 @@
package initial
import (
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
adminauthorizationservice "git.gocasts.ir/ebhomengo/niki/service/admin/authorization"
)
type AdminAuthorization struct {
AdminAuthorizationSvc adminauthorizationservice.Service
}
func InitAdminAuthorizationService(db *mysql.DB) adminauthorizationservice.Service {
return adminauthorizationservice.New(InitAdminMysql(db))
}

View File

@ -17,6 +17,7 @@ type Dependencies struct {
initial.Databases
initial.Validators
initial.Services
initial.AdminAuthorization
}
func parseFlags() bool {
@ -64,6 +65,9 @@ func initDependencies(cfg config.Config, redisAdapter redis.Adapter, db *mysql.D
AdminKindBoxReqSvc: initial.InitAdminKindBoxReqService(db),
AdminSvc: initial.InitAdminService(cfg, db),
},
initial.AdminAuthorization{
AdminAuthorizationSvc: initial.InitAdminAuthorizationService(db),
},
}
}
@ -73,7 +77,7 @@ func initAndRunServer(cfg config.Config, dependencies *Dependencies) {
dependencies.BenefactorKindBoxReqSvc, dependencies.BenefactorKindBoxReqVld,
dependencies.BenefactorAddressSvc, dependencies.BenefactorAddressVld,
dependencies.AdminSvc, dependencies.AdminVld, dependencies.AdminAuthSvc,
dependencies.AdminKindBoxReqSvc, dependencies.AdminKindBoxReqVld)
dependencies.AdminKindBoxReqSvc, dependencies.AdminKindBoxReqVld, dependencies.AdminAuthorizationSvc)
server.Serve()
}

View File

@ -1,6 +1,7 @@
package errmsg
const (
ErrorMsgAdminNotAllowed = "admin is not allowed"
ErrorMsgNotFound = "record not found"
ErrorMsgSomethingWentWrong = "something went wrong"
ErrorMsgInvalidInput = "invalid input"

View File

@ -0,0 +1,15 @@
package slice
import (
"git.gocasts.ir/ebhomengo/niki/entity"
)
func DoesExist(list []entity.AdminPermission, value entity.AdminPermission) bool {
for _, item := range list {
if item == value {
return true
}
}
return false
}

View File

@ -0,0 +1,98 @@
package mysqladmin
import (
"time"
"git.gocasts.ir/ebhomengo/niki/entity"
errmsg "git.gocasts.ir/ebhomengo/niki/pkg/err_msg"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"git.gocasts.ir/ebhomengo/niki/pkg/slice"
"git.gocasts.ir/ebhomengo/niki/repository/mysql"
)
func (d *DB) GetAdminPermissions(adminID uint, role entity.AdminRole) ([]entity.AdminPermission, error) {
const op = "mysqladmin.GetAdminPermissions"
// get adminRoleACL
adminRoleACL := make([]entity.AdminAccessControl, 0)
adminRoleRows, err := d.conn.Conn().Query(`select * from admin_access_controls where actor_type = ? and actor_id = ?`,
entity.AdminRoleActorType, role)
if err != nil {
return nil, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected)
}
defer adminRoleRows.Close()
for adminRoleRows.Next() {
acl, err := scanAccessControl(adminRoleRows)
if err != nil {
return nil, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected)
}
adminRoleACL = append(adminRoleACL, acl)
}
if err := adminRoleRows.Err(); err != nil {
return nil, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected)
}
// get adminACL
adminACL := make([]entity.AdminAccessControl, 0)
adminRows, err := d.conn.Conn().Query(`select * from admin_access_controls where actor_type = ? and actor_id = ?`,
entity.AdminAdminActorType, adminID)
if err != nil {
return nil, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected)
}
defer adminRows.Close()
for adminRows.Next() {
acl, err := scanAccessControl(adminRows)
if err != nil {
return nil, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected)
}
adminACL = append(adminACL, acl)
}
if err := adminRows.Err(); err != nil {
return nil, richerror.New(op).WithErr(err).
WithMessage(errmsg.ErrorMsgSomethingWentWrong).WithKind(richerror.KindUnexpected)
}
// merge ACLs by permission
adminPermissions := make([]entity.AdminPermission, 0)
for _, r := range adminRoleACL {
if !slice.DoesExist(adminPermissions, r.Permission) {
adminPermissions = append(adminPermissions, r.Permission)
}
}
for _, a := range adminACL {
if !slice.DoesExist(adminPermissions, a.Permission) {
adminPermissions = append(adminPermissions, a.Permission)
}
}
if len(adminPermissions) == 0 {
return nil, nil
}
return adminPermissions, nil
}
func scanAccessControl(scanner mysql.Scanner) (entity.AdminAccessControl, error) {
var (
createdAt time.Time
acl entity.AdminAccessControl
)
err := scanner.Scan(&acl.ID, &acl.ActorID, &acl.ActorType, &acl.Permission, &createdAt)
return acl, err
}

View File

@ -0,0 +1,37 @@
package adminauthorizationservice
import (
"git.gocasts.ir/ebhomengo/niki/entity"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
)
type Repository interface {
GetAdminPermissions(adminID uint, role entity.AdminRole) ([]entity.AdminPermission, error)
}
type Service struct {
repo Repository
}
func New(repo Repository) Service {
return Service{repo: repo}
}
func (s Service) CheckAccess(adminID uint, role entity.AdminRole, permissions ...entity.AdminPermission) (bool, error) {
const op = "adminauthorizationservice.CheckAccess"
AdminPermissions, err := s.repo.GetAdminPermissions(adminID, role)
if err != nil {
return false, richerror.New(op).WithErr(err)
}
for _, p := range permissions {
for _, ap := range AdminPermissions {
if p == ap {
return true, nil
}
}
}
return false, nil
}

View File

@ -2,11 +2,11 @@ package benefactorkindboxreqservice
import (
"context"
"time"
"git.gocasts.ir/ebhomengo/niki/entity"
param "git.gocasts.ir/ebhomengo/niki/param/benefactor/kind_box_req"
richerror "git.gocasts.ir/ebhomengo/niki/pkg/rich_error"
"time"
)
func (s Service) Add(ctx context.Context, req param.KindBoxReqAddRequest) (param.KindBoxReqAddResponse, error) {