Created
September 4, 2025 05:59
-
-
Save absyah/45e8eef4d31c3b2c3e91039e83d5883a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## Project [Your App Name]: RBAC Module Design Document | |
* **Author:** [Your Name] | |
* **Date:** September 4, 2025 | |
* **Status:** Proposal | |
* **Version:** 1.0 | |
### 1\. Abstract (TL;DR) | |
This document proposes the creation of a centralized, configuration-driven Role-Based Access Control (RBAC) module for our Sinatra application. The new module will replace the current system of scattered `current_account_can_...` helper methods. | |
The proposed solution moves role and permission definitions into a human-readable `roles.yml` file, providing a single source of truth. The core logic will be encapsulated in a single `RBAC` Ruby module, which supports role inheritance and instance-level permissions (e.g., a user can only edit their *own* posts). This will significantly improve maintainability, reduce code duplication, and make our authorization logic easier to understand and manage. | |
### 2\. Background and Problem Statement | |
Currently, authorization logic is implemented via a series of helper methods in `application_helpers`, such as `current_account_can_view_xxx?` and `current_account_can_edit_yyy?`. This approach has led to several challenges as the application has grown: | |
* **Scattered Logic:** Permissions are defined in Ruby code and spread across many different helper methods, making it difficult to get a holistic view of what a specific role can do. | |
* **Code Duplication:** Similar logic is often repeated with minor variations across different helpers. | |
* **Difficult to Maintain:** Changing a role's permissions can require finding and modifying multiple methods. Onboarding new developers is also more difficult as they must learn the location and naming convention of all authorization helpers. | |
* **Lack of Flexibility:** The current system does not easily support complex rules, such as object-level permissions (e.g., "an `editor` can delete their own `post`, but not others'"). | |
### 3\. Goals and Scope | |
#### 3.1. Goals | |
* **Centralize** all role and permission definitions. | |
* **Decouple** permission configuration (the "what") from authorization logic (the "how"). | |
* Provide a clean, readable API for checking permissions in both routes and views. | |
* Support **role inheritance** to keep definitions DRY (Don't Repeat Yourself). | |
* Support **instance-level permissions** based on object attributes. | |
* Improve the overall maintainability and scalability of our access control system. | |
#### 3.2. Non-Goals (Out of Scope for this Version) | |
* A user interface for managing roles and permissions. | |
* Integration with external authentication/directory systems (e.g., LDAP, OAuth scopes). | |
* Real-time permission updates without an application restart. | |
### 4\. Proposed Solution: High-Level Design | |
The proposed system consists of three core components: | |
1. **Configuration (`config/roles.yml`):** A YAML file that defines all roles, their permissions, and any inheritance rules. This file serves as the single, human-readable source of truth for our permission structure. | |
2. **Core Logic (`lib/rbac.rb`):** A self-contained Ruby module responsible for loading the YAML configuration, resolving permission inheritance, and providing a single method (`RBAC.can?`) to perform authorization checks. | |
3. **Sinatra Integration (Application Helpers):** Two simple helper methods that bridge the `RBAC` module with our application: | |
* `authorize!`: For use in routes to halt execution if permission is denied. | |
* `can?`: For use in views to conditionally render UI elements, returning `true` or `false`. | |
#### System Flow Diagram | |
This diagram illustrates the flow of an authorization check: a request comes into a route, which calls the `authorize!` helper. The helper uses the `RBAC.can?` method, which first consults the pre-loaded roles from the YAML file. If necessary, it then executes specific conditional logic to make a final "Allow" or "Deny" decision. | |
```mermaid | |
graph TD | |
A[Request to Sinatra Route] --> B{Route calls authorize!(:edit, @post)}; | |
B --> C[RBAC.can?(user, :edit, @post)]; | |
C --> D{Is "edit:post" in user's roles from roles.yml?}; | |
D -- Yes --> F[Return true]; | |
D -- No --> E{Is there a conditional rule for "edit:post"?}; | |
E -- Yes --> G{Execute conditional block: user.id == @post.author_id}; | |
G -- true --> F; | |
G -- false --> H[Return false]; | |
E -- No --> H; | |
F --> I[Allow Request]; | |
H --> J[Deny Request / Halt 403]; | |
``` | |
### 5\. Detailed Design | |
#### 5.1. The Configuration File (`config/roles.yml`) | |
Roles and permissions will be defined in a simple key-value format. | |
```yaml | |
# config/roles.yml | |
admin: | |
permissions: | |
- "*:*" # Wildcard grants all permissions | |
editor: | |
inherits: viewer # Inherits all permissions from 'viewer' | |
permissions: | |
- "create:post" | |
- "edit:post" | |
# Note: "delete:post" is intentionally omitted for editors. | |
viewer: | |
permissions: | |
- "read:post" | |
``` | |
#### 5.2. The Core Module (`lib/rbac.rb`) | |
The module will expose three primary public methods. | |
* `RBAC.load_roles(file_path)`: Called once at application startup to load the YAML file, process inheritance, and cache the resulting permissions hash. | |
* `RBAC.can?(user, action, resource_or_type, roles)`: The main authorization method. It checks for both static role permissions and dynamic, conditional permissions. | |
* `RBAC.define_conditional_permission(action, resource, &block)`: A DSL-style method used during application startup to define complex, instance-level rules in pure Ruby. | |
**Example: Defining a conditional rule in `app.rb`** | |
```ruby | |
# configure block in app.rb | |
configure do | |
# ... | |
RBAC.define_conditional_permission(:delete, :post) do |user, post| | |
user.id == post.author_id | |
end | |
end | |
``` | |
#### 5.3. Sinatra Helpers | |
The helpers provide a clean and expressive API for the rest of the application. | |
* **`authorize!(action, resource)`**: Used in routes. Fetches the resource first, then checks permission. | |
```ruby | |
# In a route | |
delete '/posts/:id' do | |
@post = Post.find(params[:id]) | |
authorize! :delete, @post # Halts with 403 if permission is denied | |
# ... proceed with deletion | |
end | |
``` | |
* **`can?(action, resource)`**: Used in views. Returns a boolean. | |
```html | |
<!-- In a view (ERB) --> | |
<% if can?(:delete, @post) %> | |
<button>Delete Post</button> | |
<% end %> | |
``` | |
### 6\. Migration Plan | |
We will adopt a phased approach to migrate from the old system to the new one, ensuring no disruption. | |
1. **Phase 1 (Introduction):** Add the `rbac.rb` module, `roles.yml` file, and the new `authorize!` and `can?` helpers to the application. The old helpers will remain functional. | |
2. **Phase 2 (Gradual Refactoring):** Go through the application controller by controller, and view by view, replacing calls to old helpers (`current_account_can_...`) with calls to the new helpers. This can be done incrementally through multiple pull requests. | |
3. **Phase 3 (Deprecation):** Once all calls to the old helpers have been replaced, perform a codebase search to ensure no usages remain, and then delete the old helper methods. | |
### 7\. Alternatives Considered | |
1. **Keep the Existing System:** This was rejected due to the maintainability and scalability issues outlined in the problem statement. | |
2. **Use an Existing Gem (e.g., Pundit, CanCanCan):** While powerful, these gems often introduce a higher level of complexity and dependencies than required for our current needs. The proposed lightweight, custom solution is tailored specifically to our application's structure and avoids "dependency bloat." It provides the exact functionality we need without any overhead. | |
### 8\. Open Questions | |
* Should we implement caching for the loaded roles configuration for performance, or is the on-startup load sufficient? (Initial assessment: startup load is fine for now). | |
----- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment