access_grant 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.codegraph/.gitignore +5 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +98 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +33 -0
  7. data/CONTRIBUTING.md +99 -0
  8. data/Gemfile +11 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +123 -0
  11. data/Rakefile +12 -0
  12. data/docs/architecture.md +1157 -0
  13. data/docs/proposal.md +143 -0
  14. data/docs/superpowers/plans/2026-09-08-access-grant-v1.md +468 -0
  15. data/docs/superpowers/plans/2026-09-08-gem-release.md +367 -0
  16. data/docs/superpowers/specs/2026-09-05-owner-role-design.md +271 -0
  17. data/docs/superpowers/specs/2026-09-07-proposal-review.md +71 -0
  18. data/docs/superpowers/specs/2026-09-07-usage-scenarios.md +301 -0
  19. data/docs/superpowers/specs/2026-09-08-gem-release-design.md +82 -0
  20. data/lib/access_grant/catalog/dsl.rb +138 -0
  21. data/lib/access_grant/catalog.rb +76 -0
  22. data/lib/access_grant/configuration.rb +55 -0
  23. data/lib/access_grant/controller_methods.rb +104 -0
  24. data/lib/access_grant/models/permission.rb +36 -0
  25. data/lib/access_grant/models/role.rb +152 -0
  26. data/lib/access_grant/models/role_permission.rb +11 -0
  27. data/lib/access_grant/owner.rb +144 -0
  28. data/lib/access_grant/permission_key.rb +29 -0
  29. data/lib/access_grant/railtie.rb +17 -0
  30. data/lib/access_grant/recovery.rb +90 -0
  31. data/lib/access_grant/sync.rb +68 -0
  32. data/lib/access_grant/tenant.rb +47 -0
  33. data/lib/access_grant/user.rb +102 -0
  34. data/lib/access_grant/version.rb +5 -0
  35. data/lib/access_grant.rb +125 -0
  36. data/lib/generators/access_grant/install/install_generator.rb +22 -0
  37. data/lib/generators/access_grant/install/templates/create_access_grant_tables.rb.tt +39 -0
  38. data/lib/generators/access_grant/setup/setup_generator.rb +188 -0
  39. data/lib/generators/access_grant/setup/templates/access_grant.rb.tt +80 -0
  40. data/lib/generators/access_grant/setup/templates/create_access_grant_user_roles.rb.tt +14 -0
  41. data/lib/generators/access_grant/setup/templates/permissions.rb.tt +10 -0
  42. data/lib/generators/access_grant/setup/templates/roles.rb.tt +27 -0
  43. data/lib/tasks/access_grant_tasks.rake +27 -0
  44. metadata +121 -0
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Boot wiring for AccessGrant. Every config.* option is listed below with
4
+ # defaults and examples — see docs/architecture.md Configuration reference.
5
+
6
+ AccessGrant.configure do |config|
7
+ # --- tenant_class (String or nil; default: nil = single-tenant) ---
8
+ # Host model that owns roles. When set, roles get a tenant FK and
9
+ # permitted? requires tenant:.
10
+ <% if multi_tenant? -%>
11
+ config.tenant_class = "<%= tenant_class %>"
12
+ # Single-tenant: omit or set nil
13
+ # config.tenant_class = nil
14
+ <% else -%>
15
+ # config.tenant_class = "Organization"
16
+ config.tenant_class = nil
17
+ <% end -%>
18
+
19
+ # --- user_class (String; default: "User") ---
20
+ # Host model that receives roles and permitted?.
21
+ config.user_class = "<%= user_class %>"
22
+ # config.user_class = "Account"
23
+
24
+ # --- owner_role (Symbol: :protected | :bypass | :both | :none; default: :protected) ---
25
+ # How privileged the Owner role is.
26
+ config.owner_role = :<%= owner_role %>
27
+ # config.owner_role = :none # no special Owner; grant_owner! raises
28
+
29
+ # --- owner_role_name (String; default: "Owner") ---
30
+ # Reserved role name for the privileged floor (case-insensitive).
31
+ config.owner_role_name = "Owner"
32
+ # config.owner_role_name = "Super Admin"
33
+
34
+ # --- tables (Hash; default: short names) ---
35
+ # Physical table names chosen by setup --tables=auto|simple|prefixed.
36
+ # If install migration still uses short names but these are prefixed,
37
+ # edit the install migration before db:migrate.
38
+ config.tables = {
39
+ roles: "<%= tables[:roles] %>",
40
+ permissions: "<%= tables[:permissions] %>",
41
+ role_permissions: "<%= tables[:role_permissions] %>",
42
+ user_roles: "<%= tables[:user_roles] %>"
43
+ }
44
+ # After collision / --tables=prefixed:
45
+ # config.tables = {
46
+ # roles: "access_grant_roles",
47
+ # permissions: "access_grant_permissions",
48
+ # role_permissions: "access_grant_role_permissions",
49
+ # user_roles: "access_grant_user_roles"
50
+ # }
51
+
52
+ # --- default_permission_actions (Array<String>) ---
53
+ # Actions emitted for each `resource :name` in the catalog DSL.
54
+ config.default_permission_actions = %w[index show create update destroy]
55
+ # config.default_permission_actions = %w[index show create update destroy search attach detach]
56
+
57
+ # --- current_user_method (Symbol; default: :current_user) ---
58
+ # Controller method used by access_grant_authorize! for the acting user.
59
+ config.current_user_method = :current_user
60
+ # config.current_user_method = :current_account
61
+
62
+ # --- current_tenant_method (Symbol; default: :current_tenant) ---
63
+ # Controller method for the tenant (multi-tenant). Host must define it.
64
+ # Unused when tenant_class is nil.
65
+ config.current_tenant_method = :current_tenant
66
+ # config.current_tenant_method = :current_organization
67
+
68
+ # --- on_tenant_created (Proc or nil; default: nil) ---
69
+ # Usually set in config/access_grant/roles.rb — seed default roles, not Owner.
70
+ # config.on_tenant_created = ->(tenant) { ... }
71
+
72
+ # --- recover_access (Proc or nil; default: built-in Recovery.grant_role!) ---
73
+ # Ops lockout recovery for rake access_grant:grant_role.
74
+ # config.recover_access = ->(role_name:, user_id:, tenant_id: nil) {
75
+ # AccessGrant::Recovery.grant_role!(role_name:, user_id:, tenant_id:)
76
+ # }
77
+ end
78
+
79
+ # Load catalog + roles after boot config.
80
+ Rails.root.glob("config/access_grant/**/*.rb").sort.each { |f| require f }
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
4
+ def change
5
+ create_table :<%= @user_roles_table %>, id: false do |t|
6
+ t.bigint :user_id, null: false
7
+ t.bigint :role_id, null: false
8
+ end
9
+ # permitted? starts from user; unique prevents duplicate assignments
10
+ add_index :<%= @user_roles_table %>, %i[user_id role_id], unique: true
11
+ # last-Owner assignment_count / reverse: users for a role
12
+ add_index :<%= @user_roles_table %>, :role_id
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Permission catalog — code is the source of truth for keys.
4
+ # Sync into the DB: bundle exec rake access_grant:sync_permissions
5
+
6
+ AccessGrant.permissions do
7
+ resource :invoices do
8
+ action :couple, description: "Can couple invoices together"
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Default roles seeded when a tenant is created (multi-tenant).
4
+ # Edit this file freely — it is your app's starting point, not Owner assignment.
5
+ # Owner stays host-owned: org.grant_owner!(user).
6
+ #
7
+ # Prerequisite: run `bundle exec rake access_grant:sync_permissions` so the
8
+ # permission catalog exists in the DB before roles reference keys.
9
+
10
+ AccessGrant.configure do |config|
11
+ config.on_tenant_created = ->(tenant) do
12
+ # Per-resource Viewer + Manager roles from synced Permission.category
13
+ # (e.g. resource :invoices → "Invoices Viewer", "Invoices Manager").
14
+ # Create-only: existing role names for this tenant are left alone.
15
+ AccessGrant::Role.ensure_resource_defaults_for!(tenant)
16
+
17
+ # Or define explicit names/keys yourself:
18
+ # AccessGrant::Role.ensure_defaults_for!(
19
+ # tenant,
20
+ # "Admin" => %w[invoices.index invoices.show invoices.update],
21
+ # "Member" => %w[invoices.index invoices.show]
22
+ # )
23
+ end
24
+ end
25
+
26
+ # Single-tenant: there is no tenant create callback. From db/seeds.rb (after sync):
27
+ # AccessGrant::Role.ensure_resource_defaults_for!(nil)
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :access_grant do
4
+ # Sync {AccessGrant.catalog} into the permissions table. Never deletes keys.
5
+ # For :protected/:both, reattaches all keys to Owner roles.
6
+ # Run on every deploy after migrate.
7
+ desc "Upsert catalog permissions into the database (never deletes)"
8
+ task sync_permissions: :environment do
9
+ AccessGrant::Sync.call
10
+ end
11
+
12
+ # Ops recovery: ROLE=Owner USER_ID=1 TENANT_ID=42 rake access_grant:grant_role
13
+ # Uses config.recover_access when set, else AccessGrant::Recovery.grant_role!.
14
+ desc "Grant a named role to a user (ops lockout recovery). Env: ROLE, USER_ID, optional TENANT_ID"
15
+ task grant_role: :environment do
16
+ role_name = ENV.fetch("ROLE") { raise "ROLE is required" }
17
+ user_id = Integer(ENV.fetch("USER_ID") { raise "USER_ID is required" })
18
+ tenant_id = ENV["TENANT_ID"]&.then { |id| Integer(id) }
19
+
20
+ callable = AccessGrant.config.recover_access
21
+ if callable
22
+ callable.call(role_name: role_name, user_id: user_id, tenant_id: tenant_id)
23
+ else
24
+ AccessGrant::Recovery.grant_role!(role_name: role_name, user_id: user_id, tenant_id: tenant_id)
25
+ end
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: access_grant
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - SahSantoshh
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: railties
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '7.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '7.0'
40
+ description: |
41
+ AccessGrant gives Rails apps a role-based access control system where permissions
42
+ live in the database and can be reassigned to roles by tenant admins at runtime,
43
+ with no deploy required. Unlike Pundit/CanCanCan/Action Policy (which hardcode
44
+ permission logic in Ruby policy/ability classes) or Rolify (which manages role
45
+ assignment but has no concept of permissions), AccessGrant ships a code-defined
46
+ permission catalog synced into the database, dynamic per-tenant roles, and a
47
+ permitted?(key) check.
48
+ email:
49
+ - sahsantoshh@gmail.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - ".codegraph/.gitignore"
55
+ - ".rspec"
56
+ - ".rubocop.yml"
57
+ - ".ruby-version"
58
+ - CHANGELOG.md
59
+ - CONTRIBUTING.md
60
+ - Gemfile
61
+ - LICENSE.txt
62
+ - README.md
63
+ - Rakefile
64
+ - docs/architecture.md
65
+ - docs/proposal.md
66
+ - docs/superpowers/plans/2026-09-08-access-grant-v1.md
67
+ - docs/superpowers/plans/2026-09-08-gem-release.md
68
+ - docs/superpowers/specs/2026-09-05-owner-role-design.md
69
+ - docs/superpowers/specs/2026-09-07-proposal-review.md
70
+ - docs/superpowers/specs/2026-09-07-usage-scenarios.md
71
+ - docs/superpowers/specs/2026-09-08-gem-release-design.md
72
+ - lib/access_grant.rb
73
+ - lib/access_grant/catalog.rb
74
+ - lib/access_grant/catalog/dsl.rb
75
+ - lib/access_grant/configuration.rb
76
+ - lib/access_grant/controller_methods.rb
77
+ - lib/access_grant/models/permission.rb
78
+ - lib/access_grant/models/role.rb
79
+ - lib/access_grant/models/role_permission.rb
80
+ - lib/access_grant/owner.rb
81
+ - lib/access_grant/permission_key.rb
82
+ - lib/access_grant/railtie.rb
83
+ - lib/access_grant/recovery.rb
84
+ - lib/access_grant/sync.rb
85
+ - lib/access_grant/tenant.rb
86
+ - lib/access_grant/user.rb
87
+ - lib/access_grant/version.rb
88
+ - lib/generators/access_grant/install/install_generator.rb
89
+ - lib/generators/access_grant/install/templates/create_access_grant_tables.rb.tt
90
+ - lib/generators/access_grant/setup/setup_generator.rb
91
+ - lib/generators/access_grant/setup/templates/access_grant.rb.tt
92
+ - lib/generators/access_grant/setup/templates/create_access_grant_user_roles.rb.tt
93
+ - lib/generators/access_grant/setup/templates/permissions.rb.tt
94
+ - lib/generators/access_grant/setup/templates/roles.rb.tt
95
+ - lib/tasks/access_grant_tasks.rake
96
+ homepage: https://github.com/SahSantoshh/access_grant
97
+ licenses:
98
+ - MIT
99
+ metadata:
100
+ homepage_uri: https://github.com/SahSantoshh/access_grant
101
+ source_code_uri: https://github.com/SahSantoshh/access_grant
102
+ changelog_uri: https://github.com/SahSantoshh/access_grant/blob/main/CHANGELOG.md
103
+ rubygems_mfa_required: 'true'
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: 3.1.0
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 3.6.9
119
+ specification_version: 4
120
+ summary: Dynamic, database-backed, per-tenant role and permission management for Rails.
121
+ test_files: []