thevahidal / soul

🕉 A SQLite REST and realtime server
https://thevahidal.github.io/soul/
MIT License
1.45k stars 50 forks source link

Secure soul installation #138

Closed IanMayo closed 4 months ago

IanMayo commented 10 months ago

I started a discussion in the users and roles issue (#4) related to how Soul could be installed, with access only allowed to a Soul Extension located within the application itself.

Then the installed extension can access the full REST API, but no other external software can.

This doesn't handle the users and roles feature requirement, it just avoids it - since the installed extension can handle authentication and authorisation.

An example use case for this feature would be to serve a react-admin application with secure access to a private SQLite backend provided by soul.

Soul extensions

IanMayo commented 10 months ago

Hello @thevahidal - I welcome your thoughts on the 3 possible strategies for delivering this feature.

Actually, it looks like @AbegaM and I have got stuck. We can't quite picture how we statically compile our extension with it being able to call JS code in Soul at run-time. I guess Soul would have to declare/expose some method(s) that is used by the compiler in our JS app - which get resolved at run-time.

Aah, hold on. I may be describing the impossible. The extension code is being served from our server, but the code itself is being run on the client browser. So, the extension and soul are being run in different environments, potentially on different devices. Maybe HTML is the only way for them to communicate. Maybe the route to fixing this is to encrypt/obfuscate the messaging between systems in a way that can't be exploited by an inquisitive user in Chrome Dev Tools.

I'm not familiar with a practice/technology that would allow this. Do you know @thevahidal ?

thevahidal commented 10 months ago

Hi @IanMayo, It seems that the most effective approach to address the problem at hand is to implement authentication and authorization for Soul. Since both the Soul API and extensions are served from a single Express instance, we can explore the option of disabling the core API and enabling only the extensions. Although this approach may require some additional effort, it has the potential to provide a viable solution.

To begin, I suggest starting with the implementation of a basic authentication system that includes roles such as "admin" and "user." This will help ensure that only authorized individuals can access the Soul API and its extensions. Once the authentication system is in place, we can gradually enhance it to support more sophisticated authentication and authorization mechanisms as needed.

Please let me know what you think.

IanMayo commented 10 months ago

Hello @thevahidal , so - we would add a users table with a specific schema, right?

Well, it must have:

fields, but could have other domain-specific ones, too.

Hmm, maybe not the "user" and "admin" fields, since the extension could handle the authorisation, we would just be implementing authentication in this phase.

thevahidal commented 10 months ago

One thing that I think we should implement before auth is the middleware extensions. Using middlewares we can enable all current APIs to go through specific steps to check the user's auth status.

Other than that I think having a is_superuser or is_admin boolean beside a roles field which can be defined and assigned by admins is a good structure. For that, we're gonna need another table in charge of storing different types of roles.

By the way, we should also update our rows endpoints to restrict the calls on the users endpoint, which only allows is_superuser users to be able to access them. Also, I think it's good to have a naming convention for the tables that we create, such as this users table. e.g. They must always start with an underscore _users.

IanMayo commented 10 months ago

That sounds good @thevahidal .

Any chance you could draw this up, or present the next level of detail in some way?

I'm happy for @AbegaM and me to start moving it foward.

thevahidal commented 10 months ago

Sure, @IanMayo, I've prepared a preliminary draft for your consideration.

Default Permissions Table

When Soul boots up, it should check for the existence of a table called _default_permissions.

_default_permissions

table_name: string
can_create: boolean default false
can_read: boolean default true
can_update: boolean default false
can_delete: boolean default false

For each existing table in the database, a row should be inserted into the _default_permissions table with the default values. Note that system-defined (private) tables, such as _default_permissions itself or tables starting with an underscore, should have all four permission fields (can_create, can_read, can_update, can_delete) set to false.

For example, a row in the _default_permissions table could look like this:

table_name: "albums"
can_create: false
can_read: true
can_update: true
can_delete: false

In this scenario, calling POST and GET requests to /tables/albums/rows is allowed by any user (even anonymous), while PUT and DELETE requests are not allowed. Each field in the row represents the default permission for a specific operation, where false means the permission is not allowed and true means it's allowed. This table allows us to set default roles for a fresh Soul setup.

Users Table

The _users table should have the following columns:

_users:
- id: autoincrement int
- username: string (unique)
- first_name: string
- last_name: string
- hashed_password: string
- is_superuser: boolean default false
- roles: many-to-many relationship with the `_roles` table

Users with is_superuser: true are able to access private operations as well.

Authentication

Now that we have default permissions in place, we can introduce new endpoints for obtaining JWT access tokens and refreshing them. JWT tokens are a good choice for authentication as they enable stateless authentication.

The JWT access token consists of the following claims:

- username: string
- is_superuser: boolean
- roles: array of role names

The "Obtain Access Token" endpoint accepts the username and password as input and returns an access token and a refresh token.

To sign the JWT token, we need to have a new environment variable called SECRET_KEY.

We can also introduce a "Register" endpoint for user registration, but it can be skipped for now.

Authentication Middleware

To enforce access control based on the default permissions table, an Express.js middleware should be implemented. This middleware checks if the endpoint and method are restricted according to the default permissions table and determines whether the user is allowed to access it or not.

Additionally, we can consider introducing a new type of extension called "middlewares." These middlewares would be similar to extensions in that they allow users to define new endpoints for Soul. Middlewares would help users define global middleware functions that can be applied to multiple routes. We can further discuss this concept in the future.

Roles

Roles provide a more sophisticated access control mechanism. While roles are an important aspect, we can focus on completing the design and implementation of the default permissions and authentication first. Once those components are in place, we can dedicate our efforts to working on the roles functionality.

This approach allows us to build a solid foundation for the access control system and progressively enhance it with role-based permissions.

IanMayo commented 10 months ago

Hey - that's a great piece of work @thevahidal

@AbegaM and I had been trying to design something - but your proposal seems perfectly logical/

IanMayo commented 10 months ago

Aah, @thevahidal - do you think an MVP is possible without the roles table? Or do you just picture a pre-MVP that has users and authentication, then we add roles for actual MVP?

thevahidal commented 10 months ago

@IanMayo, I believe prioritizing the completion of user management and authentication before diving into role implementation is a good decision. Treating it as an MVP allows us to establish a foundation for restricting certain operations on specific tables. However, implementing Role-based Access Control would undoubtedly take the system to the next level in terms of security and access management.

thevahidal commented 10 months ago

Also I wanted to give you a shoutout for your fantastic design! I couldn't help but notice how much synergy there is between our ideas. I truly believe that by blending elements from both of our designs, we can create something truly amazing.