goaccess

module
v1.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 13, 2021 License: GPL-3.0

README

goaccess

Go package to grant and check access to users with JWT, roles, access and permissions. The principle of goaccess package is that a web site or app is structured based on modules, submodules and sections. For example the modules could be the options in the main menu, the submodules are the options for each menu and the sections are the rendered page sections. goaccess handle the structure in a JSON with this schema:

{
  "vehicles": {
    "module": "vehicles",
    "access": true,
    "submodules": [
      {
        "submodule": "brand",
        "access": false,
        "sections": {
          
        }
      },
      {
        "submodule": "reception",
        "access": true,
        "sections": {
          "add": false,
          "finder": true
        }
      }
    ]
  }
}

On the other hand, goaccess package has the concept of actions in order to handle user permissions. actions are related to the submodules and are plain strings (whatever string that make sense for identify permissions) that could be active or not. For example, you can use strings based on the API endpoints the web app will use, here an schema example:

{
  "module": "vehicles",
  "submodules": [
    {
      "submodule": "brand",
      "actionList": {
        "delete:brand:[]": "Delete brand",
        "delete:brand:[]:remove": "Delete brand remove",
        "patch:brand:[]:restore": "Modify brand restore",
        "post:brand": "Create brand",
        "put:brand:[]": "Update brand"
      }
    },
    {
      "submodule": "vehicle",
      "actionList": {
        "delete:vehicle:[]:photo:[]": "Delete vehicle photo",
        "post:vehicle": "Create vehicle",
        "post:vehicle:[]:photo": "Create vehicle photo",
        "put:vehicle:[]": "Update vehicle"
      }
    }
  ]
}

where : replace the / and [] is a placeholder for the URL params. It is not necessary to have get endpoints in the list, because they are controlled on the module > submodule > section access.

Installation

go get github.com/StevenRojas/goaccess

Setup environment

JWT
export JWT_SECRET_KEY=secret!
export JWT_EXPIRE_HOURS=2
export JWT_REFRESH_HOURS=7
Redis
export REDIS_ADDR=localhost:6379
export REDIS_DB=10
export REDIS_PASS=secret

Initialize Services

Read configuration from environment variables:

serviceConfig, err := configuration.Read()

Redis client:

redisClient := redis.NewClient(&redis.Options{
	Addr: serviceConfig.Redis.Addr,
	Password: serviceConfig.Redis.Pass,
	DB: serviceConfig.Redis.DB,
})

Repositories:

initRepo, err := repository.NewInitRepository(ctx, redisClient)
usersRepo, err := repository.NewUsersRepository(ctx, redisClient)
modulesRepo, err := repository.NewModulesRepository(ctx, redisClient)
rolesRepo, err := repository.NewRolesRepository(ctx, redisClient)
actionsRepo, err := repository.NewActionsRepository(ctx, redisClient)

JWT handler:

jwtHander := utils.NewJwtHandler(serviceConfig.Security)

Services (the use of each one is explined at the corresponding sections):

service.NewAuthenticationService(usersRepo, jwtHander)
service.NewInitService(initRepo, jsonHandler)
service.NewAccessService(modulesRepo, rolesRepo, actionsRepo, subscriberFeed)
service.NewAuthorizationService(modulesRepo, rolesRepo, actionsRepo)

Initialization Service

In order to initialize Redis database with modules, submodules, sections and actions is necessary to have a set of json files in folders init/modules and init/actions, check the examples. Then run the following:

path, _ := os.Getwd()
path = path + "/init"
jsonHandler := utils.NewJSONHandler(path)
initService := service.NewInitService(initRepo, jsonHandler)
initService.Init(ctx, true) // true to force recreate the DB

The code above will parse all json files at init folder and setup Redis.

In order to generate the JSON files, you can use the following utility code that parse a Postman collection and generate the JSON file for a given module:

path, _ := os.Getwd()
path = path + "/postman"
postman := utils.NewPostmanParser(path, true) // use postman description
j, err := postman.Parse("collection.json", "vehicles")

where j is the JSON schema, collection.json is the postman filename and vehicles is the module name. The utility will generate a description based on the URL or will use the Postman description attribute. Of course, the JSON generated for this utility should be reviewed and improved, the sectionList is empty, so it should be filled out manually.

Authentication Service

This service handle the logic to handle user authentication using JWT access and refresh tokens. It is possible to login, logout, vefiry the access token and refresh the token when it expires.

s := service.NewAuthenticationService(usersRepo, jwtHander)

Register a user: add a user in the DB, The ID is not autogerated because it suppose there is another module like HR that has a CRUD for users

err = s.Register(context.TODO(), &entities.User{
	ID: "1",
	Email: "steven.rojas@gmail.com",
	Name: "Steven Rojas",
	IsAdmin: false,
})

Unregister a user: remove user from the DB

err = s.Unregister(context.TODO(), &entities.User{
	ID: "1",
	Email: "steven.rojas@gmail.com",
	Name: "Steven Rojas",
	IsAdmin: false,
})

Login a user: validates if the user email is active and returns a user structure (id, email, name, admin) and the access and refresh tokens. The calims of the access token contains only the user_id

{
  "access_uuid": "bua7kgbc1osgrba1e160",
  "exp": 1603566265,
  "user_id": "1"
}
loggedUser, err := s.Login(context.TODO(), "steven.rojas@gmail.com")
// loggedUser.User
// loggedUser.Token.Access
// loggedUser.Token.Refresh

Verify access token: Verify if the token is valid and it doesn't expired, returns the user_id

id, err := s.VerifyToken(context.TODO(), "c3NfdXVpZ...")

Refresh access token: Refresh the access token after validate that the refresh token is still valid, the method returns a new token pair

token, err := s.RefreshToken(context.TODO(), "c3NfdXVpZ...")

Logout: Logout the user for the given token.

err := s.Logout(context.TODO(), &entities.Token{
	Access: "c3NfdXVpZ...",
	Refresh: "eyJleHAiO...",
})

Access Service

Access service generates events when roles changes, for example when a module or an action is assigned/unassigned to/from a role. So it is necessary to define subscribers that will update the user's access and permissions as follow:

subscriberFeed := events.NewSubscriber()
accessListener := events.NewAccessListener(subscriberFeed)
go accessListener.RegisterAccessListener() // Listen for access changes
actionListener := events.NewActionListener(subscriberFeed)
go actionListener.RegisterActionListener() // Listen for action changes

Then, with the subscriberFeed creates the access service instance:

s := service.NewAccessService(modulesRepo, rolesRepo, actionsRepo, subscriberFeed)
Handle roles

With the access service you can add, update and remove roles

// Add role
roleID, err := s.AddRole(ctx, "accounting manager") // roleID = 'r1'
// Update role
err := s.EditRole(ctx, "r1", "accounting manager updated")
// Delete role
err := accessService.DeleteRole(ctx, "r2")

In order to get the JSON schema for render modules and actions while creating a new role, use these two methods:

modules, err := s.ModulesList(ctx)
actions, err := s.ActionsForNewRole(ctx)
Handle modules

You can assign and unassign modules, submodules and sections to a role with the following methods:

// Assign modules
err := s.AssignModules(ctx, "r1", []string{"vehicles"})
// Unassign modules
err := s.UnassignModules(ctx, "r1", []string{"hr", "bank"})
// Assign submodules to a module
err := s.AssignSubModules(ctx, "r1", "vehicles", []string{"reception", "work-category"})
// Unassign submodules
err := s.UnassignSubModules(ctx, "r1", "vehicles", []string{"work-category"})
// Assign sections to a module > submodule
err := s.AssignSections(ctx, "r1", "vehicles", "reception", []string{"finder", "add"})
// Unassign sections
err := s.UnassignSections(ctx, "r4", "vehicles", "reception", []string{"finder"})

Authorization Service

This service allows to assign and unassign roles to/from users, handle role actions and check if a user has permissions to perform an specific action as follow:

s := service.NewAuthorizationService(modulesRepo, rolesRepo, actionsRepo)
// Assign actions to a module > submodule
err := s.AssignActions(ctx, "r1", "vehicles", "brand", []string{"delete:brand:[]:remove"})
// Unassign actions
err := s.UnassignActions(ctx, "r1", "vehicles", "brand", []string{"delete:brand:[]:remove"})
// Assign role to a user
err := s.AssignRole(ctx, "1", "r2")
// Unassign role from a user
err := s.UnassignRole(ctx, "1", "r2")
// Get access JSON for a given user
accessJSON, err := s.GetAccessList(ctx, "1")
// Get action JSON for a given user and module
actionsJSON, err := s.GetActionListByModule(ctx, "vehicle", "1")
// Check if a user has permission to execute an action
hasPermission, err := s.CheckPermission(ctx, "delete|vehicle|brand|[]", "1")

Directories

Path Synopsis
cmd
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL