User & access system
Exiry backend uses an opinionated access system. The system divides the User
s into Role
s, and every Role
has many Access
es and every Access
has its own Privileges
.
In recap: a User hasOne Role hasMany [ Access has Privileges ]. Check out the GraphQL schema below for more details.
Example case: In this page, we'll be using a Sales management tool like a CRM for example, you can check a recap accesses and privileges table in the end.
Privileges: the 5 fundamentals
Every access in the system can have up to 5 fundamental privileges:
- can view: Can view all the data in the store*
- can view own: Can view only the data that he owns** in the store*
- can create: can create new data in the store*
- can edit: can update data in the store*
- can delete: can delete data in the store*
User can only Edit or Delete that he can view.
So if he has a canViewOwn, he will be - if granted - able to edit or delete his own data.
canView
has a priority on canViewOwn
.
If a user has a canView
privilege on an Access
, canViewOwn
is ignored.
* the store: can be any data storage like SQL databases.
** data owned: can be different depending on the data, for example: own Invoices in a CRM are Invoice edited by the User or Invoices of the customers that the sales-person (User) manages.
Accesses
An Access
can be anything, depending on your design. But most of the time, an access represents a set of data, for example Invoices, Projects or Customers.
Not every Access
needs to have the 5 fundamental privileges, for example, in the CRM app we can have an Access
with 2 privileges (canView and canViewOwn) like Reports, dashboard, or bulk data exports.
Accesses have two String attributes name
and slug
. Both are in english and they do not change with user's UI language. The name
is displayed in the Administration part of the UI and needs to be easily understandable, the slug
is slugified automatically generated from the name
. The developer can add an optional description
field if the name
brings confusion.
Let's say in continuity to our above context, we are building a Sales management tool like a CRM. We are going to have in database some data relative to invoices like:
- tbl_invoices
- tbl_invoices_items
And we'll be creating an Access
named Invoices
and every data delivered via our Query and Mutation relative to those two database tables will be protected by the Invoices
access.
Roles
Roles are basically groups of users, most of the time, roles will be parallel to in-life roles, for example, if we are building a Sales management tool like a CRM, roles gone be:
- General manager will have a 1 member for example
- Sales manager will have a 1 member for example
- Sales-person will have a 10 members for example
- Accountant will have a 1 member for example
Recap example table
For Role
General manager
Access | View | View Own | Create | Edit | Delete |
---|---|---|---|---|---|
Invoices | ✓ | -- | ✓ | ✓ | ✓ |
For Role
Sales manager
Access | View | View Own | Create | Edit | Delete |
---|---|---|---|---|---|
Invoices | ✓ | -- | ✓ | ✓ | ✓ |
For Role
Sales-person
Access | View | View Own | Create | Edit | Delete |
---|---|---|---|---|---|
Invoices | ✕ | ✓ | ✓ | ✕ | ✕ |
For Role
Accountant
Access | View | View Own | Create | Edit | Delete |
---|---|---|---|---|---|
Invoices | ✓ | -- | ✕ | ✓ | ✕ |
The Role
General manager and Sales manager have the same access and privileges in this example. In a real life situation, some roles can have similar accesses and privileges but generally not for there accesses.
GraphQL schema
type User {
...
role: Role
}
type Role {
id: ID!
name: String
accesses: [Access]
}
type Access {
id: ID!
name: String!
description: String
slug: String!
isPage: Boolean
privileges: Privileges
allowView: Boolean
allowViewOwn: Boolean
allowEdit: Boolean
allowCreate: Boolean
allowDelete: Boolean
}
type Privileges {
id: ID!
canView: Boolean
canViewOwn: Boolean
canEdit: Boolean
canCreate: Boolean
canDelete: Boolean
}